diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -1,546 +1,1420 @@ -# -*- coding: utf-8 -*- import gradio as gr -import google.generativeai as genai -from google.api_core import exceptions as google_exceptions +import requests import os import json +import PyPDF2 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.") +import cv2 +import numpy as np from datetime import datetime from ratelimiter import RateLimiter import difflib from html import escape import random import logging -import traceback -import re +import re # Importado para uso em funções como calcular_progresso e gerar_caso_ficticio -logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - [%(funcName)s] - %(message)s') +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) -load_dotenv() -GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") or os.environ.get("GEMINI_API_KEY") +# A chave API deve ser configurada como Secret no Hugging Face +GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") if not GEMINI_API_KEY: - raise ValueError("❌ Chave GEMINI_API_KEY não encontrada.") - -try: - genai.configure(api_key=GEMINI_API_KEY) - logger.info("Biblioteca google-generativeai configurada.") -except Exception as e: - raise ValueError(f"❌ Erro config google-generativeai: {e}") - -GEMINI_MODEL_NAME = "gemini-1.5-flash-latest" -CONFIG = {"max_retry_attempts": 3, "retry_delay": 5, "timeout_api": 120, "temperatura": 0.3, "max_tokens": 8192} -rate_limiter = RateLimiter(max_calls=15, period=60) - -SAFETY_SETTINGS = [ - {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"}, - {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_MEDIUM_AND_ABOVE"}, - {"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"}, - {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"}, -] + raise ValueError("❌ A chave GEMINI_API_KEY não foi encontrada. Configure-a como Secret nas configurações do Space.") + +# Usando o endpoint gemini-1.5-flash que demonstrou funcionar no Colab +GEMINI_API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key={GEMINI_API_KEY}" + +CONFIG = { + "max_retry_attempts": 3, + "retry_delay": 5, + "timeout": 120, + "temperatura": 0.3, + "max_tokens": 8192 +} + +rate_limiter = RateLimiter(max_calls=30, 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"] +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": "Direto, evite jargões.", - "Fundamentação Legal": "Cite leis, súmulas, jurisprudência.", - "Revisão Ortográfica": "Evite erros de português.", - "Endereçamento (Inicial)": "Verifique competência.", - "Pedidos (Inicial)": "Certos, determinados, líquidos.", - "Preliminares (Contestação)": "Art. 337 CPC antes do mérito.", - "Impugnação Específica (Contestação)": "Conteste cada fato (Art. 341)." + "Clareza e Concisão": "Seja direto e evite jargões excessivos. Facilite a leitura do juiz.", + "Fundamentação Legal": "Sempre cite os artigos de lei, súmulas e jurisprudência pertinentes ao seu argumento.", + "Coerência Argumentativa": "Mantenha uma linha lógica de raciocínio do início ao fim da peça.", + "Revisão Ortográfica e Gramatical": "Erros de português podem prejudicar a credibilidade da sua peça.", + "Formatação": "Utilize parágrafos bem definidos, espaçamento adequado e destaque pontos importantes (negrito, itálico) com moderação.", + "Endereçamento (Inicial)": "Em relações de consumo, prefira o foro do domicílio do autor (Art. 101, I, CDC). Verifique a competência (material, territorial).", + "Qualificação (Inicial)": "Inclua todos os dados do Art. 319, II, CPC. Use 'XXX' para dados não fornecidos.", + "Fatos (Inicial)": "Narrativa cronológica, clara e objetiva, sem juízo de valor.", + "Pedidos (Inicial)": "Certos, determinados e líquidos (sempre que possível). Cumule pedidos se necessário (Art. 327, CPC).", + "Custas (Inicial)": "Sempre peça a juntada da guia ou a gratuidade de justiça (com declaração).", + "Valor da Causa (Inicial)": "Deve corresponder ao benefício econômico pretendido (Art. 292, CPC).", + "Preliminares (Contestação)": "Alegue todas as matérias do Art. 337 do CPC antes de discutir o mérito.", + "Impugnação Específica (Contestação)": "Conteste cada fato alegado pelo autor, sob pena de presunção de veracidade (Art. 341, CPC).", + "Mérito (Contestação)": "Apresente sua versão dos fatos e a fundamentação jurídica que ampara a improcedência dos pedidos do autor.", + "Pedidos (Contestação)": "Requeira o acolhimento das preliminares e/ou a improcedência total dos pedidos do autor, além da condenação em custas e honorários.", + "Pressupostos Recursais": "Verifique cabimento, tempestividade, preparo (custas recursais), regularidade formal e interesse/legitimidade.", + "Dialeticidade (Recurso)": "Impugne especificamente os fundamentos da decisão recorrida (Art. 1010, II e III, CPC para Apelação).", + "Delimitação da Matéria (Recurso)": "Indique claramente qual parte da decisão você está recorrendo.", + "Fumus Boni Iuris": "Demonstre a plausibilidade do direito alegado com base em provas e argumentos sólidos.", + "Periculum in Mora": "Evidencie o risco de dano irreparável ou de difícil reparação se a medida não for concedida imediatamente.", + "Reversibilidade": "Se possível, argumente que a medida é reversível caso a decisão final seja diferente (Art. 300, §3º, CPC).", } HISTORICO = [] CORRECAO_CACHE = {} -def preprocess_image_for_ocr(img_pil): - if not CV2_AVAILABLE: - logger.warning("OpenCV indisponível, usando conversão simples.") - return img_pil.convert('L') +def preprocess_image_for_ocr(img): 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) + gray = img.convert('L') + np_img = np.array(gray) + _, thresh = cv2.threshold(np_img, 150, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) return Image.fromarray(thresh) except Exception as e: - logger.error(f"Erro pré-processamento OpenCV: {e}") - return img_pil.convert('L') + logger.error(f"Erro no pré-processamento da imagem: {str(e)}") + return f"❌ Erro no pré-processamento: {str(e)}" def extract_text_from_pdf(file_path_or_obj): - text = ""; ocr_text_parts = [] + text = "" + ocr_text = "" try: - logger.info(f"Abrindo PDF: {getattr(file_path_or_obj, 'name', file_path_or_obj)}") - with pdfplumber_open(file_path_or_obj) as pdf: - logger.info(f"PDF com {len(pdf.pages)} páginas.") - for i, page in enumerate(pdf.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ág {i+1} sem texto, tentando OCR.") - try: - img = page.to_image(resolution=200) - preprocessed_img = preprocess_image_for_ocr(img.original) - page_ocr = pytesseract.image_to_string(preprocessed_img, lang='por', config='--psm 6') - if page_ocr and page_ocr.strip(): ocr_text_parts.append(page_ocr) - else: logger.warning(f"OCR vazio pág {i+1}."); ocr_text_parts.append(f"[OCR P{i+1}: Vazio]") - except Exception as e: logger.error(f"Erro OCR pág {i+1}: {e}"); ocr_text_parts.append(f"[Erro OCR P{i+1}: {e}]") - ocr_text = "\n".join(ocr_text_parts) - len_text, len_ocr = len(text.strip()), len(ocr_text.strip()) - if len_text > len_ocr * 0.7: logger.info(f"Usando texto PDF ({len_text} chars)."); return text - elif len_ocr > 0: logger.info(f"Usando OCR PDF ({len_ocr} chars)."); return ocr_text - elif len_text > 0: logger.info("Usando texto curto PDF."); return text - else: logger.warning("PDF sem texto."); return "⚠️ PDF sem texto (extração/OCR falharam)." - except Exception as e: logger.error(f"Erro PDF: {e}", exc_info=True); return f"❌ Erro PDF: {e}" + pdf_reader = pdfplumber_open(file_path_or_obj) + for i, page in enumerate(pdf_reader.pages): + page_text = page.extract_text() + if page_text: + text += page_text + "\n" + else: + logger.warning(f"Página {i+1} sem texto extraível, tentando OCR.") + try: + # Supondo que Tesseract esteja instalado via packages.txt no HF + img = page.to_image(resolution=300) + preprocessed_img = preprocess_image_for_ocr(img.original) + page_ocr_text = pytesseract.image_to_string(preprocessed_img, lang='por') + if page_ocr_text.strip(): + ocr_text += page_ocr_text + "\n" + else: + logger.warning(f"OCR falhou na página {i+1}.") + ocr_text += f"[OCR falhou na página {i+1}]\n" + except Exception as ocr_e: + logger.error(f"Erro no OCR da página {i+1}: {str(ocr_e)}") + ocr_text += f"[Erro no OCR da página {i+1}: {str(ocr_e)}]\n" + pdf_reader.close() + + if text.strip() and len(text) > len(ocr_text) * 0.5: + logger.info("Usando texto extraído do PDF.") + return text + elif ocr_text.strip(): + logger.info("Usando texto do OCR do PDF.") + return ocr_text + else: + logger.warning("Não foi possível extrair texto significativo do PDF.") + return "⚠️ Não foi possível extrair texto do PDF (tentativa de OCR falhou ou texto inexistente)." + + except Exception as e: + logger.error(f"Erro geral ao processar o PDF: {str(e)}") + try: + logger.info("Tentando fallback com PyPDF2.") + text_fallback = "" + if isinstance(file_path_or_obj, str): + with open(file_path_or_obj, 'rb') as f: + reader = PyPDF2.PdfReader(f) + for page in reader.pages: + text_fallback += page.extract_text() or "" + else: + file_path_or_obj.seek(0) + reader = PyPDF2.PdfReader(file_path_or_obj) + for page in reader.pages: + text_fallback += page.extract_text() or "" + + if text_fallback.strip(): + logger.info("Texto extraído com fallback PyPDF2.") + return text_fallback + else: + return f"❌ Erro ao processar o PDF e fallback falhou: {str(e)}" + except Exception as fallback_e: + logger.error(f"Erro no fallback com PyPDF2: {str(fallback_e)}") + 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([p.text for p in doc.paragraphs if p.text]) - return text if text.strip() else "⚠️ DOCX vazio." - except Exception as e: logger.error(f"Erro DOCX: {e}", exc_info=True); return f"❌ Erro DOCX: {e}" + text = "" + for para in doc.paragraphs: + text += para.text + "\n" + 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)}") + return f"❌ Erro ao processar o DOCX: {str(e)}" def extract_text_from_txt(file_path_or_obj): try: - content = b''; encodings = ['utf-8', 'latin-1', 'cp1252'] if isinstance(file_path_or_obj, str): - with open(file_path_or_obj, 'rb') as f: content = f.read() + 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 TXT: {seek_e}") content = file_path_or_obj.read() - else: return "❌ Input TXT inválido." - for enc in encodings: + if hasattr(file_path_or_obj, 'seek'): + try: file_path_or_obj.seek(0) + except: pass + else: + return "❌ Tipo de entrada TXT inválido." + + encodings = ['utf-8', 'latin-1', 'cp1252', 'iso-8859-1'] + for encoding in encodings: try: - decoded = content.decode(enc) - logger.info(f"TXT decodificado: {enc}.") - return decoded if decoded.strip() else "⚠️ TXT vazio." - except UnicodeDecodeError: continue - return "❌ Não foi possível decodificar TXT." - except Exception as e: logger.error(f"Erro TXT: {e}", exc_info=True); return f"❌ Erro TXT: {e}" + 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)}") + return f"❌ Erro ao processar o TXT: {str(e)}" def process_file(file_obj): - if file_obj is None: return "⚠️ Nenhum arquivo." + if file_obj is None: + logger.warning("Nenhum arquivo fornecido para processamento.") + return "⚠️ Nenhum arquivo enviado." try: - fpath = getattr(file_obj, 'name', None); - if not fpath: return "❌ Erro: Não identificou arquivo." - fname = os.path.basename(fpath).lower(); logger.info(f"Processando: {fname}") + file_path = getattr(file_obj, 'name', None) + if not file_path: + logger.error("Não foi possível obter o nome do arquivo do objeto.") + return "❌ Erro: Não foi possível identificar o tipo do arquivo." + + filename = os.path.basename(file_path).lower() + logger.info(f"Processando arquivo: {filename}") + try: - fsize = os.path.getsize(fpath); logger.info(f"Tamanho: {fsize} bytes") - if fsize > 15*1024*1024: return "⚠️ Arquivo grande (Máx 15MB)." - except OSError as e: logger.warning(f"Erro tamanho arquivo: {e}") - if fname.endswith('.pdf'): return extract_text_from_pdf(fpath) - elif fname.endswith('.docx'): return extract_text_from_docx(fpath) - elif fname.endswith('.txt'): return extract_text_from_txt(fpath) - else: return "⚠️ Formato não suportado (PDF, DOCX, TXT)." - except Exception as e: logger.error(f"Erro process_file: {e}", exc_info=True); return f"❌ Erro processar arquivo: {e}" + 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} excede o limite de tamanho.") + return "⚠️ O arquivo é muito grande. Máx. 15 MB." + 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:** 1.Análise Geral. 2.Nota Final(0-10). 3.Pontos Positivos. 4.Pontos a Melhorar(Desc, Fund., Impacto, Sug.). 5.Análise Requisitos. - **Critérios:** Coerência, Fundamentação, Linguagem, Pedidos. NÃO INVENTAR FATOS(-3a-5). NÃO SE IDENTIFICAR(Nota ZERO).""" - if modo_treinamento: base += "\n**Treinamento:** Detalhar Pontos a Melhorar(a-e). Destacar inconsistências. Sugerir estratégias." + **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 = f"## Análise: {tipo_peticao}\n**Caso:**\n```\n{caso}\n```\n**Peça Aluno:**\n```\n{peticao}\n```\n{instrucoes_gerais}\n**Requisitos({tipo_peticao}):**\n" - if tipo_peticao == "Petição Inicial": prompt += "-End. Comp(319,I;CDC101,I).\n-Qualif(319,II).\n-Fatos.\n-Fundam(319,III).\n-Pedidos(319,IV).\n-Vlr Causa(291-2,319,V).\n-Provas(319,VI).\n-Aud(319,VII).\n-Citação.\n-Docs essenc.\n-Fecho.\n**Adic:**'XXX'.Falta custas/JG(-1).Vírgula pede defer.(-0.2)." - elif tipo_peticao == "Contestação": prompt += "-End. Qualif réu. Proc nº. Temp.\n-Prelim(337):TODAS.\n-Impug.Esp Fatos(341):PONTOxPONTO.\n-Mérito:Versão réu;defesa dir/indir.\n-Fundam defesa.\n-Pedidos:Acolher prelim;IMPROC TOTAL;Sucumb(85).\n-Provas.\n**Adic:**Prelim preclui?Falha impug(-2a-4).Sem ped improc(-1.5)." - elif tipo_peticao == "Reconvenção": prompt += "-NA MESMA PEÇA(343).End. Proc nº.Qualif.\n-Conexão(343).\n-Estrut PI:Fatos,Fund,Ped,Vlr,Provas(reconv).\n-Ped intim(343§1).\n**Adic:**S/conex(-2).Falta ped/vlr/prov(-1/item).Peça separada(-3)." - elif tipo_peticao == "Impugnação à Contestação": prompt += "-End.Proc nº.Autor.Temp(15d-350/1).\n-Resp Prelim.\n-Resp Fatos Novos(350).\n-Resp Docs(437).\n-Ratific Inicial.\n-Espec Provas.\n-Peds:Rej prelim;refutar def;proced inicial.\n**Adic:**Ñ resp prelim(-1.5).Ñ manif fato novo(-2).Ñ ratificar(-0.5)." - elif tipo_peticao == "Embargos de Declaração": prompt += "-Contra Decisão Espec.End juiz prolator.Proc nº.Partes.Temp(5d-1023).\n-Cabim(1022):Apontar PRECISAMENTE:Omissão/Contrad/Obscur/ErroMat.\n-NÃO REJULGAR.\n-Ped:Conhec e ACOLHER p/ sanar vício.\n**Adic:**Rediscutir mérito(-4).Ñ apontar vício(-3)." - elif tipo_peticao == "Recurso de Apelação": prompt += "-Interpos:Juízo a quo.Proc nº.Partes.Pressup(cabim 1009;temp 15d;prep/JG).Ped receb/remessa.\n-Razões:TRIBUNAL.Relato.Fundam Reforma/Anul(1010):Error in proc/judic.DIALETICIDADE.\n-Peds:Conhec;PROVIM(anul/ref);Inv sucumb.\n**Adic:**Falta peça(-10).End razões(-1).S/dialetic(-3a-5)." - elif tipo_peticao == "Agravo de Instrumento": prompt += "-Peça única TRIBUNAL.Proc origem.Partes+Advs(end 1016).Cópia Decisão(1017,I).\n-Cabim(1015/mitig).Temp(15d).Prep/JG.\n-Peças Obrig(1017,I):Dec,Intim,Proc.Facult(III).\n-Razões(1016):Motivos ref/inval.\n-Efeito Susp/Tut Antecip Rec(1019,I)(se risco).\n-Peds Finais:Conhec;Efeito/Tut;PROVIM;Comunic juiz.\n**Adic:**Falta peça obrig(-4).S/cabim(-3)." - elif tipo_peticao == "Petição de Tutela Provisória": prompt += "-Tipo(Urg/Evid).Caráter(Antec/Incid).End.Qualif.Proc(se incid).\n-Urg(300):Fumus Boni Iuris E Periculum in Mora.Reversib(300§3).\n-Evid(311):Hipótese I-IV.NÃO precisa periculum.\n-Ped Específico:Medida liminar CLARA.\n-Se Antec(303/5):Indicar ped final ou aditar.Vlr Causa.\n-Peds:Concessão tutela;cit/intim;indic ação/adit.\n**Adic:**S/Fumus/Peric(-3a-5).Erro Evid(-3).Ped genérico(-2)." - else: prompt += "-Adequação? Clareza? Fundam? Pedido?" - prompt += "\n\n**Instruções Finais:** Analise estritamente. Rigoroso e construtivo. Gere **exclusivamente** no formato solicitado." - return prompt + 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 que fundamentam a reconvenção. + - Fundamentação jurídica do pedido reconvencional. + - Pedidos específicos da reconvenção (condenação do autor-reconvindo). + - Valor da causa para a reconvenção. + - Indicação de provas para a reconvenção. + - 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 para a reconvenção: -1.0 por item. + * Apresentada em peça separada da contestação (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 (prazo de 15 dias - Art. 350/351 CPC). + - **Resposta às Preliminares:** Rebater cada preliminar arguida na contestação. + - **Resposta a Fatos Impeditivos/Modificativos/Extintivos:** Se o réu alegou fatos novos (Art. 350), o autor DEVE se manifestar sobre eles. + - **Resposta a Documentos Novos:** Manifestar-se sobre documentos juntados com a contestação (Art. 437 CPC). + - **Ratificação da Inicial:** Reiterar os termos e pedidos da petição inicial. + - **Especificação de Provas:** Se o réu apresentou defesa de mérito indireta ou preliminares que exigem prova, o autor deve especificar as provas que pretende produzir para contrapor (Art. 350/351). + - Pedidos: Rejeição das preliminares, refutação da defesa de mérito, procedência dos pedidos da inicial. + - Fechamento formal. + **Critérios Adicionais (Impugnação):** + * Não responder a uma preliminar relevante: -1.5 pontos. + * Não se manifestar sobre fato novo impeditivo/modificativo/extintivo alegado: -2.0 pontos. + * Não ratificar a inicial: -0.5 pontos. + """ + elif tipo_peticao == "Embargos de Declaração": + prompt_especifico += """ + - **Contra Decisão Judicial Específica:** Indicar claramente qual decisão está sendo embargada (sentença, acórdão, decisão interlocutória). + - Endereçamento ao PRÓPRIO juízo que proferiu a decisão. + - Indicação do processo. + - Qualificação das partes (embargante e embargado). + - Tempestividade (prazo de 5 dias - Art. 1023 CPC). + - **Cabimento (Art. 1022, CPC):** Apontar **PRECISAMENTE** e **FUNDAMENTADAMENTE** a ocorrência de: + - **Omissão:** Ponto ou questão sobre o qual o juiz deveria ter se pronunciado e não o fez. + - **Contradição:** Afirmações conflitantes DENTRO da própria decisão. + - **Obscuridade:** Falta de clareza que impede a compreensão da decisão. + - **Erro Material:** Engano manifesto e corrigível de ofício (nome, cálculo, etc.). + - **NÃO PODE REJULGAR A CAUSA:** O objetivo é sanar vício, não modificar o mérito (salvo efeitos infringentes excepcionais). + - **Pedido:** Requerer o conhecimento dos embargos e seu ACOLHIMENTO para sanar o vício apontado (com ou sem efeitos infringentes, se for o caso). + - Fechamento formal. + **Critérios Adicionais (Embargos):** + * Usar Embargos para rediscutir o mérito (erro grave): -4.0 pontos. + * Não apontar claramente o vício (omissão/contradição/obscuridade/erro material): -3.0 pontos. + * Endereçar ao tribunal em vez do juízo prolator (erro): -1.0 ponto. + """ + elif tipo_peticao == "Recurso de Apelação": + prompt_especifico += """ + - **Peça de Interposição:** + - Endereçamento ao JUIZ que proferiu a sentença (juízo a quo). + - Indicação do processo. + - Qualificação das partes (apelante e apelado). + - Demonstração dos pressupostos de admissibilidade (cabimento - contra sentença Art. 1009; tempestividade - 15 dias Art. 1003, §5º; preparo - juntada das guias ou pedido de JG; regularidade formal). + - Pedido de recebimento do recurso, processamento e remessa ao Tribunal (juízo ad quem). + - **Razões Recursais:** + - Endereçamento ao TRIBUNAL competente (Ex: Egrégio Tribunal de Justiça). + - Breve exposição dos fatos e do ocorrido no processo (resumo da lide e da sentença). + - **Fundamentos de Fato e de Direito para Reforma/Anulação (Art. 1010, II e III):** + - **Error in procedendo:** Erro na condução do processo (ex: cerceamento de defesa). Leva à anulação. + - **Error in judicando:** Erro no julgamento do mérito (má aplicação da lei, má valoração da prova). Leva à reforma. + - **Princípio da Dialeticidade:** Impugnar ESPECIFICAMENTE os fundamentos da sentença recorrida. Não pode ser cópia da inicial/contestação. + - **Pedidos:** Requerer o conhecimento do recurso; o PROVIMENTO para ANULAR ou REFORMAR a sentença (especificar o que se quer: procedência, improcedência, etc.); inversão do ônus da sucumbência. + - Fechamento formal (Local, data, Advogado, OAB). + **Critérios Adicionais (Apelação):** + * Falta da peça de interposição ou das razões: Nota zero. + * Endereçamento incorreto das razões (para o juiz a quo): -1.0 ponto. + * Ausência de impugnação específica aos fundamentos da sentença (violação dialeticidade): -3.0 a -5.0 pontos. + * Não pedir anulação/reforma de forma clara: -1.5 pontos. + * Falta de comprovação do preparo ou pedido de JG: Recurso deserto (na prática). Avaliar como erro grave (-2.0). + """ + elif tipo_peticao == "Agravo de Instrumento": + prompt_especifico += """ + - **Peça Única (Interposição e Razões):** + - Endereçamento diretamente ao TRIBUNAL competente (Presidente ou Relator sorteado). + - Indicação do processo de origem (número, vara, comarca). + - Qualificação completa das partes (agravante e agravado) e seus advogados (com endereços - Art. 1016, IV). + - Indicação da decisão agravada (cópia integral OBRIGATÓRIA - Art. 1017, I). + - **Cabimento (Art. 1015, CPC ou teses de taxatividade mitigada):** Demonstrar que a decisão interlocutória se encaixa numa das hipóteses legais ou na urgência decorrente da inutilidade futura do julgamento do mérito (Tema 988 STJ). + - **Tempestividade:** Prazo de 15 dias (Art. 1003, §5º). + - **Preparo:** Juntada das guias ou pedido de JG. + - **Peças Obrigatórias (Art. 1017, I):** Cópia da decisão agravada, certidão de intimação (ou outro doc. que comprove tempestividade), procurações das partes. + - **Peças Facultativas (Art. 1017, III):** Outras peças úteis para compreensão da controvérsia. + - **Razões do Pedido de Reforma/Invalidação (Art. 1016, II e III):** Exposição do fato e do direito; razões pelas quais a decisão deve ser alterada. Foco no erro da decisão interlocutória. + - **Pedido de Efeito Suspensivo ou Tutela Antecipada Recursal (Art. 1019, I):** Se houver risco de dano grave, pedir fundamentadamente. + - **Pedidos Finais:** Requerer o conhecimento do recurso; (se aplicável) a concessão de efeito suspensivo/tutela antecipada; o PROVIMENTO do agravo para reformar ou invalidar a decisão agravada; a comunicação ao juiz da causa (Art. 1018 - opcional se processo eletrônico). + - Fechamento formal. + **Critérios Adicionais (Agravo):** + * Ausência de peça obrigatória (decisão, intimação, procurações): Inadmissibilidade (na prática). Erro gravíssimo (-4.0). + * Não demonstrar o cabimento (Art. 1015 ou mitigação): -3.0 pontos. + * Endereçar ao juiz a quo: -1.0 ponto. + * Não indicar o nome e endereço dos advogados: Falha formal (-0.5). + """ + elif tipo_peticao == "Petição de Tutela Provisória": + prompt_especifico += """ + - **Tipo (Urgência ou Evidência):** A petição deve deixar claro qual modalidade se busca. Pode ser requerida em caráter antecedente ou incidental. + - Endereçamento ao juízo competente (se antecedente) ou ao juízo da causa (se incidental). + - Qualificação completa das partes. + - Indicação do processo (se incidental). + - **Tutela de Urgência (Art. 300 CPC):** + - **Fumus Boni Iuris:** Demonstrar a **probabilidade do direito** alegado. Argumentação e provas pré-constituídas. + - **Periculum in Mora:** Demonstrar o **perigo de dano** ou o risco ao resultado útil do processo se tiver que esperar a decisão final. Urgência concreta. + - **Reversibilidade (Art. 300, §3º):** Argumentar, se possível, que a medida pode ser revertida caso a decisão final seja improcedente. + - **Tutela de Evidência (Art. 311 CPC):** + - Demonstrar uma das hipóteses: I - abuso do direito de defesa/propósito protelatório; II - alegações comprovadas apenas documentalmente + tese firmada em julgamento repetitivo ou súmula vinculante; III - pedido reipersecutório com prova documental adequada do contrato de depósito; IV - inicial instruída com prova documental suficiente + réu não opõe prova capaz de gerar dúvida razoável. + - **NÃO precisa demonstrar periculum in mora.** + - **Pedido Específico:** Indicar CLARAMENTE qual medida liminar está sendo solicitada (obrigação de fazer/não fazer, bloqueio de bens, etc.). + - **Se Antecedente (Art. 303 ou 305):** Indicar o pedido final a ser formulado ou apresentar a petição inicial completa no prazo legal. + - **Valor da Causa:** Se antecedente, estimar. Se incidental, geralmente já existe. + - **Pedidos:** Requerer a concessão da tutela provisória (liminarmente ou após justificação prévia); citação/intimação do réu; (se antecedente) indicação da futura ação principal ou aditamento. + - Fechamento formal. + **Critérios Adicionais (Tutela):** + * Não demonstrar claramente o Fumus Boni Iuris ou o Periculum in Mora (para Urgência): -3.0 a -5.0 pontos. + * Não enquadrar corretamente numa das hipóteses da Tutela de Evidência: -3.0 pontos. + * Pedido genérico ou pouco claro sobre a medida desejada: -2.0 pontos. + * Confundir os requisitos de Urgência e Evidência: -1.5 pontos. + """ + else: + prompt_especifico += """ + - **Adequação ao Tipo:** A peça corresponde minimamente à estrutura e finalidade do tipo selecionado? + - **Clareza:** É possível entender o que o aluno pretende com a peça? + - **Fundamentação Mínima:** Há alguma tentativa de basear os argumentos em lei ou fatos? + - **Pedido:** Existe um pedido, mesmo que mal formulado? + """ + + 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, especialmente nos 'Pontos a Melhorar'. Gere a resposta **exclusivamente** no formato solicitado (Análise Geral, Nota, Pontos Positivos, etc.)." + return prompt_especifico def estruturar_prompt_peticao_corrigida(caso, peticao_original, tipo_peticao): - prompt = f"**Caso:**\n```\n{caso}\n```\n**Peça Original ({tipo_peticao}):**\n```\n{peticao_original}\n```\n**Tarefa:** Gere versão corrigida ({tipo_peticao}), seguindo práticas OAB.\n**Instruções ({tipo_peticao}):**\n" - if tipo_peticao == "Petição Inicial": prompt += "1.End. 2.Qualif(319,II,'XXX'). 3.Fatos. 4.Fundam. 5.Pedidos(quantif). 6.Vlr Causa(292). 7.Provas. 8.Aud(319,VII). 9.Citação(246,I). 10.Custas/JG(NÃO OMITIR). 11.Fecho('Nestes termos...')." - elif tipo_peticao == "Contestação": prompt += "1.End. 2.ID(proc nº,réu). 3.Temp. 4.Estrut:Síntese;Prelim(337);Mérito(Impug+Defesa);Reconv(se antes peds cont). 5.Peds Cont:Acolher prelim;IMPROC TOTAL;Sucumb. 6.Provas réu. 7.Fecho." - elif tipo_peticao == "Reconvenção": prompt += "*Parte da Contest.* 1.Após mérito cont, antes peds: 'DA RECONVENÇÃO'. 2.Qualif Recvte/Recvdo. 3.Conexão. 4.Fatos/Dir Recv. 5.Peds Recv. 6.Vlr Causa/Provas Recv. 7.Intim Recvdo. 8.Peds Cont. 9.Fecho." - elif tipo_peticao == "Impugnação à Contestação": prompt += "1.End,proc,autor,temp. 2.Tópico PRELIM - Rebater. 3.Tópico MÉRITO/FATOS NOVOS - Manifestar. 4.Tópico RATIFICAÇÃO. 5.Tópico PROVAS. 6.Peds:Rej prelim, refutar def, proced inicial. 7.Fecho." - elif tipo_peticao == "Embargos de Declaração": prompt += "1.End juiz prolator.Proc,Partes.Decisão ID.Temp(5d). 2.Tópico p/ vício(1022):OMISSÃO/CONTRAD/OBSCUR/ERRO MAT. 3.Dentro:Trecho + Explicar vício. Ñ REJULGAR. 4.Ped:Conhec e ACOLHER p/ sanar. 5.Fecho." - elif tipo_peticao == "Recurso de Apelação": prompt += "1.Interpos:Juízo a quo.Proc,Partes.Pressup(cab,temp,prep/JG).Ped receb/remessa. 2.Razões:Tribunal.Relato.FUND REFORMA/ANUL(Error in proc/judic).DIALETICIDADE. 3.Peds:Conhec;PROVIM(anul/ref);Inv sucumb. 4.Fecho." - elif tipo_peticao == "Agravo de Instrumento": prompt += "1.Peça única Trib.Proc origem,Partes,Advs(ends).Decisão ID(cópia). 2.CABIM(1015/mitig).TEMP.PREP/JG.PEÇAS OBRIG/FACULT. 3.RAZÕES:Motivos ref/inval. 4.EFEITO SUSP/TUT ANTEC(se cabível). 5.PEDS:Conhec;Efeito/Tut;PROVIM;Comunic juiz. 6.Fecho." - elif tipo_peticao == "Petição de Tutela Provisória": prompt += "1.TIPO(Urg/Evid) e CARÁTER(Antec/Incid).End.Qualif.Proc(se incid). 2.REQUISITOS:Urg=Fumus+Peric(+Reversib).Evid=Hipót 311. 3.PED LIMINAR:Medida CLARA. 4.Se Antec:Indic ped final/aditar.Vlr Causa. 5.PEDS:Concessão tutela;cit/intim;indic ação/adit. 6.Fecho." - else: prompt += "1.Estrutura. 2.Args Claros. 3.Ped Claro. 4.Formais OAB." - prompt += "\n\n**Instrução Final:** Gere **APENAS** texto da peça corrigida (endereçamento até 'OAB/...'). Sem comentários." + prompt = f""" + **Caso/Contexto Fornecido:** + ``` + {caso} + ``` + + **Peça Original do Aluno ({tipo_peticao}):** + ``` + {peticao_original} + ``` + + **Tarefa:** Com base no caso e nos problemas identificados na peça original (implícitos na sua estrutura/conteúdo), **gere uma versão corrigida e aprimorada** da peça ({tipo_peticao}). Siga **rigorosamente** as melhores práticas e os 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 e justificado). + 2. **Qualificação:** Completa (Art. 319, II), usando "XXX" para dados ausentes no caso. + 3. **Fatos:** Narre de forma cronológica, clara e fiel ao caso, sem invenções. + 4. **Fundamentação:** Cite artigos de lei pertinentes (ex: CC, CDC, CPC) e, se aplicável, súmulas/jurisprudência relevante. + 5. **Pedidos:** Claros, certos, determinados (quantificar danos se possível com base no caso). Incluir pedidos cumulados se cabível (principal, danos morais, materiais, etc.). + 6. **Valor da Causa:** Correto (soma dos benefícios econômicos - Art. 292). + 7. **Provas:** Indicar as provas de forma genérica mas adequada (ex: "protesta provar o alegado por todos os meios de prova em direito admitidos, em especial documental, testemunhal e pericial"). + 8. **Audiência:** Indicar opção pela audiência (Art. 319, VII). + 9. **Citação:** Pedido de citação do réu (ex: "via postal, nos termos do Art. 246, I, CPC"). + 10. **Custas:** Requerimento expresso de juntada da guia paga OU pedido de gratuidade de justiça (se o caso sugerir hipossuficiência, com pedido de juntada de declaração). **NUNCA OMITIR.** + 11. **Fechamento:** "Nestes termos pede deferimento." (SEM vírgula). Seguido de "Local, data.", "ADVOGADO(A)", "OAB/...". **NUNCA identificar o advogado.** + """ + elif tipo_peticao == "Contestação": + prompt += """ + 1. **Endereçamento:** Ao juízo onde a ação tramita. + 2. **Identificação:** Número do processo, qualificação do réu. + 3. **Tempestividade:** Pode mencionar ("A presente contestação é tempestiva..."). + 4. **Estrutura:** + * **Síntese da Inicial:** Breve resumo do que o autor pede. + * **Preliminares (Art. 337):** Argua todas as cabíveis, fundamentando cada uma. + * **Mérito:** + * **Impugnação Específica:** Conteste cada fato relevante da inicial. + * **Defesa de Mérito:** Apresente a versão do réu, fatos impeditivos/modificativos/extintivos, e a fundamentação legal para a improcedência. + * **Reconvenção (Se houver no caso):** Incluir como tópico separado ANTES dos pedidos da contestação. + 5. **Pedidos da Contestação:** + * Acolhimento das preliminares (com a consequência - extinção/remessa). + * **IMPROCEDÊNCIA TOTAL** dos pedidos do autor. + * Condenação do autor em custas e honorários. + 6. **Provas:** Indicar as provas que o réu pretende produzir. + 7. **Fechamento:** Padrão OAB (Local, data, Advogado(a), 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 um tópico claro: "DA RECONVENÇÃO". + 2. **Qualificação:** Identifique o Réu como Reconvinte e o Autor como Reconvindo. + 3. **Conexão:** Justifique brevemente a conexão com a ação principal ou defesa. + 4. **Fatos Reconvencionais:** Narre os fatos que baseiam o pedido do reconvinte. + 5. **Direito Reconvencional:** Fundamente juridicamente o pedido do reconvinte. + 6. **Pedidos da Reconvenção:** Formule pedidos claros e específicos contra o autor-reconvindo (ex: condenação a pagar X, fazer Y). + 7. **Valor da Causa da Reconvenção:** Atribua um valor específico para a reconvenção. + 8. **Provas da Reconvenção:** Indique as provas para a reconvenção. + 9. **Intimação:** Peça a intimação do autor-reconvindo para contestar a reconvenção. + 10. **Continue com os Pedidos da Contestação** (improcedência da ação original, etc.). + 11. **Fechamento:** Padrão OAB. + """ + elif tipo_peticao == "Impugnação à Contestação": + prompt += """ + 1. **Endereçamento:** Ao juízo correto. + 2. **Identificação:** Processo, Impugnante. + 3. **Tempestividade:** Mencione o prazo de 15 dias. + 4. **Estrutura:** + * **Resposta às Preliminares:** Rebata cada preliminar arguida. + * **Resposta aos Fatos Impeditivos/Modificativos/Extintivos:** Manifeste-se sobre fatos novos (Art. 350). + * **Resposta aos Documentos:** Manifeste-se sobre documentos juntados (Art. 437). + * **Ratificação da Inicial:** Reitere os termos e pedidos iniciais. + 5. **Pedidos:** Rejeição das preliminares, refutação da defesa, procedência da ação. + 6. **Provas:** Especifique provas para contrapor a defesa, se necessário. + 7. **Fechamento:** Padrão OAB. + """ + elif tipo_peticao == "Embargos de Declaração": + prompt += """ + 1. **Endereçamento:** Ao juízo que proferiu a decisão embargada. + 2. **Identificação:** Processo, Embargante, Embargado. + 3. **Decisão Embargada:** Indique claramente qual decisão (sentença fls. X, acórdão ID Y). + 4. **Tempestividade:** Mencione o prazo de 5 dias. + 5. **Cabimento (Art. 1022):** + * Crie um tópico para cada vício alegado (Da Omissão, Da Contradição, etc.). + * Dentro de cada tópico, **transcreva o trecho relevante da decisão** e explique **objetivamente** onde está o vício. NÃO REFAÇA O JULGAMENTO. + 6. **Pedido:** Peça o conhecimento e ACOLHIMENTO dos embargos para sanar o(s) vício(s) apontado(s), especificando o que se espera (ex: "para que seja suprida a omissão quanto a...", "para que seja corrigida a contradição entre..."). Se cabível e fundamentado, pedir efeitos infringentes. + 7. **Fechamento:** Padrão OAB. + """ + elif tipo_peticao == "Recurso de Apelação": + prompt += """ + 1. **Peça de Interposição:** Ao juízo a quo (quem proferiu a sentença). + 2. **Identificação:** Processo, Apelante, Apelado. + 3. **Pressupostos:** Cabimento, tempestividade, preparo/JG. + 4. **Pedido (Interposição):** Recebimento e remessa ao Tribunal. + 5. **Razões Recursais:** Ao Tribunal (ad quem). + 6. **Resumo:** Fatos, lide, sentença. + 7. **Fundamentos (Art. 1010):** Error in procedendo/judicando. **Dialeticidade OBRIGATÓRIA**. + 8. **Pedidos (Razões):** Conhecimento, PROVIMENTO para anular/reformar, inversão sucumbência. + 9. **Fechamento:** Padrão OAB. + """ + elif tipo_peticao == "Agravo de Instrumento": + prompt += """ + 1. **Endereçamento:** Diretamente ao Tribunal. + 2. **Identificação:** Processo de origem, Agravante, Agravado, Advogados (com endereços). + 3. **Decisão Agravada:** Indicar e juntar cópia OBRIGATÓRIA. + 4. **Cabimento:** Demonstrar hipótese do Art. 1015 ou mitigação. + 5. **Pressupostos:** Tempestividade, preparo/JG. + 6. **Peças Obrigatórias (Art. 1017):** Decisão, intimação, procurações. Facultativas: outras úteis. + 7. **Razões:** Exposição do fato e direito para reforma/invalidação. + 8. **Efeito Suspensivo/Tutela Antecipada:** Pedir fundamentadamente se necessário (Art. 1019, I). + 9. **Pedidos Finais:** Conhecimento, efeito suspensivo/tutela (se houver), PROVIMENTO para reformar/invalidar. + 10. **Fechamento:** Padrão OAB. + """ + elif tipo_peticao == "Petição de Tutela Provisória": + prompt += """ + 1. **Tipo:** Urgência ou Evidência? Antecedente ou Incidental? + 2. **Endereçamento:** Juízo competente ou da causa. + 3. **Identificação:** Partes, processo (se incidental). + 4. **Requisitos:** + * **Urgência (Art. 300):** Fumus Boni Iuris + Periculum in Mora (+ Reversibilidade, se possível). + * **Evidência (Art. 311):** Uma das hipóteses legais (NÃO precisa periculum). + 5. **Pedido Específico:** Qual medida liminar é solicitada? + 6. **Se Antecedente:** Indicar pedido final ou aditar/completar no prazo. + 7. **Pedidos:** Concessão da tutela, citação/intimação. + 8. **Fechamento:** Padrão OAB. + """ + else: + prompt += """ + 1. **Estrutura Lógica:** Organize a peça com começo, meio e fim. + 2. **Argumentos:** Apresente os argumentos de forma clara. + 3. **Pedidos:** Formule um pedido claro ao final. + 4. **Formalidades:** Use linguagem adequada e o fechamento padrão 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": - p_lower = peticao_corrigida.lower() - if "juntada da guia de custas" not in p_lower and "gratuidade de justiça" not in p_lower: + if "juntada da guia de custas" not in peticao_corrigida.lower() and "gratuidade de justiça" not in peticao_corrigida.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 p_lower and "audiência de mediação" not in p_lower: + if len(partes) == 2: + peticao_corrigida = partes[0] + "Requer, 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_corrigida.lower() and "audiência de mediação" not in peticao_corrigida.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.") + if len(partes) == 2: + peticao_corrigida = partes[0] + "Opta 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 = ["Local, data.", "ADVOGADO(A)", "OAB/..."] - while linhas and any(t in linhas[-1] for t in ["ADVOGADO","OAB","Local","data"]): linhas.pop() - linhas.extend(fechamento) - return "\n".join(linhas) + 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): - logger.info(f"Chamando API Gemini (Modelo: {GEMINI_MODEL_NAME})...") - model = genai.GenerativeModel(GEMINI_MODEL_NAME, safety_settings=SAFETY_SETTINGS) - gen_config = genai.types.GenerationConfig(temperature=CONFIG["temperatura"], max_output_tokens=CONFIG["max_tokens"], top_p=0.95) + 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 com {len(prompt)} caracteres de prompt. Temperatura: {CONFIG['temperatura']}, Max Tokens: {CONFIG['max_tokens']}") + for tentativa in range(CONFIG["max_retry_attempts"]): try: with rate_limiter: - logger.debug(f"API Tentativa {tentativa + 1}") - response = model.generate_content(contents=prompt, generation_config=gen_config) - if response.prompt_feedback.block_reason: - logger.error(f"Prompt bloqueado: {response.prompt_feedback.block_reason}") - return f"❌ Prompt bloqueado: {response.prompt_feedback.block_reason}. Reformule." - if not response.text: - if response.candidates and response.candidates[0].finish_reason != genai.types.FinishReason.STOP: - finish_reason = response.candidates[0].finish_reason.name - logger.error(f"Geração interrompida (s/ texto): {finish_reason}") - safety = response.candidates[0].safety_ratings - harms = [r.category.name for r in safety if r.probability.name not in ['NEGLIGIBLE', 'LOW']] - if harms: return f"❌ Bloqueado segurança: {finish_reason} ({','.join(harms)})." - else: return f"❌ Geração interrompida: {finish_reason}." - else: logger.warning("API retornou s/ texto ou bloqueio."); return "⚠️ API retornou resposta vazia." - logger.info(f"API OK ({len(response.text)} chars).") - return response.text - except google_exceptions.ResourceExhausted as e: - logger.warning(f"Erro 429 - Tentativa {tentativa + 1}: {e}") - if tentativa < CONFIG["max_retry_attempts"] - 1: wait = CONFIG['retry_delay']*(2**tentativa); logger.info(f"Aguardando {wait}s..."); time.sleep(wait); continue - return "❌ Erro 429: Muitas requisições." - except google_exceptions.PermissionDenied as e: logger.error(f"Erro Permissão API: {e}"); return f"❌ Erro Permissão: Verifique API Key/acesso modelo '{GEMINI_MODEL_NAME}'." - except google_exceptions.InvalidArgument as e: logger.error(f"Erro Argumento API: {e}"); return f"❌ Erro parâmetros API: {e}" - except genai.types.BlockedPromptException as e: logger.error(f"Prompt bloqueado (exc): {e}"); return f"❌ Prompt bloqueado. Reformule." - except genai.types.StopCandidateException as e: logger.error(f"Geração interrompida (exc): {e}"); return f"❌ Geração interrompida (segurança/outro)." - except google_exceptions.GoogleAPIError as e: - logger.error(f"Erro API Google - Tentativa {tentativa + 1}: {e}") - if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay']*(tentativa + 1)); continue - return f"❌ Erro API Google: {e}" + logger.debug(f"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"Status Code recebido: {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). Tentando novamente em {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_info = response.json() + error_msg = error_info.get("error", {}).get("message", "Erro desconhecido na API") + logger.error(f"Erro da API (Status {response.status_code}): {error_msg}") + if tentativa < CONFIG["max_retry_attempts"] - 1: + logger.info(f"Tentando novamente em {CONFIG['retry_delay'] * (tentativa + 1)}s...") + 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") + safety_ratings = prompt_feedback.get("safetyRatings") + logger.error(f"Resposta da API sem 'candidates'. Block Reason: {block_reason}, Safety Ratings: {safety_ratings}") + if block_reason: + return f"❌ A solicitação foi bloqueada pela API. Motivo: {block_reason}. Tente reformular o caso ou a petição." + else: + return "❌ Resposta vazia ou inválida da API (sem 'candidates'). Verifique o log para detalhes." + + candidate = response_data["candidates"][0] + finish_reason = candidate.get("finishReason") + if finish_reason not in [None, "STOP", "MAX_TOKENS"]: + logger.error(f"Geração interrompida pela API. Motivo: {finish_reason}") + safety_ratings_candidate = candidate.get("safetyRatings", []) + harm_categories = [r['category'] for r in safety_ratings_candidate if r['probability'] not in ['NEGLIGIBLE', 'LOW']] + if harm_categories: + return f"❌ A resposta foi bloqueada por segurança pela API. Motivo: {finish_reason} (Categorias: {', '.join(harm_categories)}). Revise o conteúdo." + else: + return f"❌ A geração da resposta foi interrompida pela API. Motivo: {finish_reason}. Tente novamente ou simplifique a solicitação." + + if "content" not in candidate or "parts" not in candidate["content"] or not candidate["content"]["parts"]: + logger.error("Estrutura de resposta inesperada da API: Faltando content/parts.") + if tentativa < CONFIG["max_retry_attempts"] - 1: + logger.info(f"Tentando novamente em {CONFIG['retry_delay'] * (tentativa + 1)}s...") + 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. Tamanho da resposta: {len(texto_gerado)} caracteres.") + return texto_gerado + + except requests.Timeout: + logger.warning(f"Timeout na tentativa {tentativa + 1}.") + if tentativa < CONFIG["max_retry_attempts"] - 1: + logger.info(f"Tentando novamente em {CONFIG['retry_delay'] * (tentativa + 1)}s...") + time.sleep(CONFIG["retry_delay"] * (tentativa + 1)) + continue + return "⏱️ Tempo esgotado após múltiplas tentativas. Verifique a conexão ou aumente o timeout." + except requests.RequestException as e: + logger.error(f"Erro de rede/requisição na tentativa {tentativa + 1}: {e}") + if tentativa < CONFIG["max_retry_attempts"] - 1: + logger.info(f"Tentando novamente em {CONFIG['retry_delay'] * (tentativa + 1)}s...") + time.sleep(CONFIG["retry_delay"] * (tentativa + 1)) + continue + return f"❌ Erro na requisição: {e}" + except json.JSONDecodeError as e: + logger.error(f"Erro ao decodificar JSON da resposta da API: {e}") + logger.debug(f"Resposta que causou erro de 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 API - Tentativa {tentativa + 1}: {e}") - if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay']*(tentativa + 1)); continue - return f"❌ Erro inesperado API: {e}" - logger.error("Falha API após todas as tentativas.") - return "❌ Falha API após múltiplas tentativas." + logger.exception(f"Erro inesperado na chamada da API na tentativa {tentativa + 1}: {str(e)}") + if tentativa < CONFIG["max_retry_attempts"] - 1: + logger.info(f"Tentando novamente em {CONFIG['retry_delay'] * (tentativa + 1)}s...") + time.sleep(CONFIG["retry_delay"] * (tentativa + 1)) + continue + return f"❌ Erro inesperado: {str(e)}" + + return "❌ Falha ao chamar a API após todas as tentativas." def corrigir_peticao(caso, tipo_peticao, peticao_texto=None, peticao_arquivo=None, modo_treinamento=False): - logger.info(f"Correção: Tipo={tipo_peticao}, Treinamento={modo_treinamento}") - start = time.time() - analise_fmt, pet_corr, res_diff, crit_diff, html_diff = "", "", "", "", "" - pet_cont = "" - try: - if not caso or not caso.strip(): return "⚠️ Forneça caso.", "", "", "", "" - if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO: return "⚠️ Selecione tipo.", "", "", "", "" - if peticao_arquivo: pet_cont = process_file(peticao_arquivo) - elif peticao_texto: pet_cont = peticao_texto - else: return "⚠️ Forneça petição.", "", "", "", "" - if pet_cont.startswith(("❌", "⚠️")): return pet_cont, "", "", "", "" - logger.info(f"Petição recebida ({len(pet_cont)} chars).") - troll = ["escreva a petição", "faça a petição", "gere a petição"] - if any(t in pet_cont.lower() for t in troll): return random.choice(["Hummm 😉","🚫 Corrijo, não crio."]), "", "", "", "" - cache_key = f"{tipo_peticao}|{caso[:100]}|{pet_cont[:100]}|{modo_treinamento}" - if cache_key in CORRECAO_CACHE: - logger.info("Cache hit.") - cached = CORRECAO_CACHE[cache_key] - return cached["analise_formatada"], cached["peticao_corrigida"], cached["resumo_diff"], cached["criticas_diff"], cached["diff_html"] - prompt_analise = estruturar_prompt_analise(caso, pet_cont, tipo_peticao, modo_treinamento) - analise_bruta = chamar_api(prompt_analise) - if analise_bruta.startswith(("❌", "⏱️", "⚠️")): return analise_bruta, "", "", "", "" - analise_fmt = formatar_correcao(analise_bruta) - prompt_peticao = estruturar_prompt_peticao_corrigida(caso, pet_cont, tipo_peticao) - pet_corr_bruta = chamar_api(prompt_peticao) - if pet_corr_bruta.startswith(("❌", "⏱️", "⚠️")): pet_corr = f"⚠️ Falha gerar corrigida:\n{pet_corr_bruta}" + logger.info(f"Iniciando correção para tipo: {tipo_peticao}") + start_time = time.time() + + 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.", "", "", "", "", "" + + peticao_conteudo = "" + 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, "", "", "", "", "" + logger.info(f"Texto extraído do arquivo ({len(peticao_conteudo)} caracteres).") + elif peticao_texto and peticao_texto.strip(): + peticao_conteudo = peticao_texto + logger.info(f"Usando texto da petição fornecido ({len(peticao_conteudo)} caracteres).") + else: + logger.warning("Nenhuma petição fornecida (nem texto, nem arquivo).") + return "⚠️ Forneça a petição (em texto ou arquivo).", "", "", "", "", "" + + 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", + "3scr3v4 a p3t1çã0", "f4ç4 a p3t1çã0", "r3d1j4 a p3t1çã0", + "3lab0r3 a p3t1çã0", "cr13 a p3t1çã0", "g3r3 a p3t1çã0", + "formule", "produza a petição", "desenvolva a petição", + "faça para mim", "faça por mim", "escreva para mim", + ] + peticao_lower = peticao_conteudo.lower() + if any(trigger in peticao_lower for trigger in troll_triggers): + respostas_troll = [ + "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? Assim você aprende de verdade!", + "🚫 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!", + "📚 Lembre-se: a prática leva à perfeição (e à aprovação!). Tente redigir a peça com base no caso, e depois eu analiso para você.", + "🤖 Sou um corretor, não um escritor fantasma! Escreva sua versão que eu aponto os caminhos para melhorar.", + "💡 Dica de ouro: Comece! Esboce a peça, mesmo que imperfeita. Depois a gente lapida juntos. Mande bala!", + "😂 Pedir pra IA fazer a peça é tipo pedir pro GPS dirigir o carro. Ele te guia, mas quem tá no volante é você! Escreva aí!", + ] + logger.info("Detectado pedido para escrever petição. Retornando resposta troll.") + return random.choice(respostas_troll), "", "", "", "", "" + + aviso_validacao = "" + + 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] + # Recalcular diff_html se não foi cacheado + diff_html_cache, _, _ = calcular_diff_interno(cached_data["peticao_original"], cached_data["peticao_corrigida"]) + return cached_data["analise_formatada"], cached_data["peticao_original"], cached_data["peticao_corrigida"], cached_data["resumo_diff"], cached_data["criticas_diff"], diff_html_cache + + + logger.info("Gerando prompt de análise...") + prompt_analise = estruturar_prompt_analise(caso, peticao_conteudo, tipo_peticao, modo_treinamento) + logger.info("Enviando para análise da API...") + analise = chamar_api(prompt_analise) + if analise.startswith("❌") or analise.startswith("⏱️") or analise.startswith("⚠️"): + logger.error(f"Erro na análise da petição: {analise}") + return aviso_validacao + analise, peticao_conteudo, "", "", "", "" + logger.info("Análise recebida da API.") + analise_formatada = formatar_correcao(analise) + + logger.info("Gerando prompt para peça corrigida...") + prompt_peticao = estruturar_prompt_peticao_corrigida(caso, peticao_conteudo, tipo_peticao) + logger.info("Enviando para geração da peça corrigida API...") + peticao_corrigida_bruta = chamar_api(prompt_peticao) + + peticao_corrigida_final = "" + if peticao_corrigida_bruta.startswith("❌") or peticao_corrigida_bruta.startswith("⏱️") or peticao_corrigida_bruta.startswith("⚠️"): + logger.error(f"Erro ao gerar peça corrigida: {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 + + if start_index != -1: + peticao_extraida = peticao_corrigida_bruta[start_index:] else: - markers = ["excelentíssimo", "eméritos julgadores", "meritíssimo juízo"] - start_index = -1 - p_lower = pet_corr_bruta.lower() - for m in markers: - idx = p_lower.find(m) - if idx != -1: - start_index = idx - break - pet_extr = pet_corr_bruta[start_index:] if start_index != -1 else pet_corr_bruta - pet_corr = pos_corrigir_peticao(pet_extr, tipo_peticao) - html_diff, res_diff, crit_diff = calcular_diff_interno(pet_cont, pet_corr) - res_cache = {"analise_formatada":analise_fmt,"peticao_corrigida":pet_corr,"resumo_diff":res_diff,"criticas_diff":crit_diff,"diff_html":html_diff} - CORRECAO_CACHE[cache_key] = res_cache - HISTORICO.append({"data":datetime.now().strftime("%Y%m%d_%H%M%S"),"tipo_peticao":tipo_peticao,"caso":caso,"peticao_original":pet_cont,**res_cache}) - if len(HISTORICO) > 10: HISTORICO.pop(0) - logger.info(f"Correção concluída em {time.time() - start:.2f}s.") - return analise_fmt, pet_corr, res_diff, crit_diff, html_diff - except Exception as e: logger.exception("Erro GERAL corrigir_peticao:"); return f"❌ Erro correção: {e}", "", "", "", "" + peticao_extraida = 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.") + + correcao_completa_formatada = aviso_validacao + analise_formatada + resultado_historico = { + "data": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "tipo_peticao": tipo_peticao, + "caso": caso, + "peticao_original": peticao_conteudo, + "analise_formatada": correcao_completa_formatada, + "peticao_corrigida": peticao_corrigida_final, + "resumo_diff": resumo_diff, + "criticas_diff": criticas_diff + } + HISTORICO.append(resultado_historico) + if len(HISTORICO) > 10: + HISTORICO.pop(0) + + CORRECAO_CACHE[cache_key] = { + "analise_formatada": correcao_completa_formatada, + "peticao_original": peticao_conteudo, + "peticao_corrigida": peticao_corrigida_final, + "resumo_diff": resumo_diff, + "criticas_diff": criticas_diff, + } + + end_time = time.time() + logger.info(f"Correção concluída em {end_time - start_time:.2f} segundos.") + + return correcao_completa_formatada, peticao_conteudo, peticao_corrigida_final, resumo_diff, criticas_diff, diff_html 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") - linhas = texto.split('\n'); linhas_fmt = [] + 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 l in linhas: - ls = l.strip() - if ls.startswith(("1.","2.","3.","4.","5.")): linhas_fmt.append(f"\n**{ls}**"); in_list = False - elif ls.startswith(("- ","* ")): prefix = " * " if in_list else "* "; linhas_fmt.append(prefix + ls[2:]); in_list = True - elif ls.startswith(("a)","b)","c)","d)","e)")): linhas_fmt.append(f" * {ls}"); in_list = True - else: linhas_fmt.append(l); in_list = False if not ls.startswith(("*","- ","a)","b)","c)","d)","e)"," ")) else in_list - txt_final = "\n".join(linhas_fmt) - m = re.search(r"(\*\*Nota Final:?\*\*\s*)(\d{1,2}[,.]\d{1}|\d{1,2})", txt_final, re.I) - if m: nota = m.group(2).replace(",","."); txt_final = re.sub(r"(\*\*Nota Final:?\*\*\s*)(\d{1,2}[,.]\d{1}|\d{1,2})", f"\\1**{nota}/10**", txt_final, count=1, flags=re.I) - return txt_final + for linha in linhas: + linha_strip = linha.strip() + if linha_strip.startswith("1.") or linha_strip.startswith("2.") or linha_strip.startswith("3.") or linha_strip.startswith("4.") or linha_strip.startswith("5."): + linhas_formatadas.append(f"\n**{linha_strip}**") + in_list = False + elif linha_strip.startswith("- ") or linha_strip.startswith("* "): + prefix = " * " if in_list else "* " + linhas_formatadas.append(prefix + linha_strip[2:]) + in_list = True + elif linha_strip.startswith("a)") or linha_strip.startswith("b)") or linha_strip.startswith("c)") or linha_strip.startswith("d)") or linha_strip.startswith("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 para dicas...*" - dicas = []; txt_l = texto_peticao.lower() - for k in ["Clareza e Concisão", "Fundamentação Legal", "Revisão Ortográfica"]: - if k in DICAS_OAB_MELHORADO: dicas.append(f"💡 **{k}:** {DICAS_OAB_MELHORADO[k]}") - kw = { "Petição Inicial": ["endereçamento","qualificação","fatos","pedidos","valor","provas","audiência","custas"], - "Contestação": ["preliminar","mérito","impugnação","art. 337"], "Embargos de Declaração": ["omissão","contradição","art. 1022"], - "Recurso de Apelação": ["sentença","reforma","dialeticidade","art. 1009"], "Agravo de Instrumento": ["interlocutória","cabimento","art. 1015"], - "Petição de Tutela Provisória": ["urgência","evidência","periculum","fumus","art. 300"]} - if tipo_peticao in kw: - for k, v in DICAS_OAB_MELHORADO.items(): - cl = k.lower() - if f"({tipo_peticao.split()[0].lower()})" in cl or any(w in cl for w in kw[tipo_peticao] if w in txt_l): - fmt = f"💡 **{k}:** {v}" - if fmt not in dicas: dicas.append(fmt) - if not dicas: return "*Continue para dicas...*" - return "\n\n".join(dicas[:4]) + if not texto_peticao or not texto_peticao.strip(): + return "Digite algo na petição para receber dicas..." + + dicas_relevantes = [] + texto_lower = texto_peticao.lower() + + for chave, dica in DICAS_OAB_MELHORADO.items(): + if chave in ["Clareza e Concisão", "Fundamentação Legal", "Coerência Argumentativa", "Revisão Ortográfica e Gramatical", "Formatação"]: + dicas_relevantes.append(f"💡 **{chave}:** {dica}") + + keywords_por_tipo = { + "Petição Inicial": ["endereçamento", "qualificação", "fatos", "fundamentos", "pedidos", "valor da causa", "provas", "audiência", "citação", "custas"], + "Contestação": ["preliminar", "mérito", "impugnação", "fatos", "improcedência", "provas", "honorários"], + "Reconvenção": ["reconvinte", "reconvindo", "conexão", "pedidos reconvencionais"], + "Impugnação à Contestação": ["réplica", "preliminar", "fatos novos", "documentos", "ratifica"], + "Embargos de Declaração": ["omissão", "contradição", "obscuridade", "erro material", "decisão embargada"], + "Recurso de Apelação": ["sentença", "reforma", "anulação", "dialeticidade", "preparo", "tribunal", "razões"], + "Agravo de Instrumento": ["decisão interlocutória", "tribunal", "cabimento", "urgência", "efeito suspensivo", "peças obrigatórias"], + "Petição de Tutela Provisória": ["urgência", "evidência", "liminar", "periculum", "fumus boni", "probabilidade", "risco"], + } + + if tipo_peticao in keywords_por_tipo: + for keyword in keywords_por_tipo[tipo_peticao]: + if keyword in texto_lower: + for chave, dica in DICAS_OAB_MELHORADO.items(): + chave_lower = chave.lower() + if f"({tipo_peticao.split()[0].lower()})" in chave_lower or keyword in chave_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[:5]) def calcular_diff_interno(texto_original, texto_corrigido): - if not texto_original or not texto_corrigido or texto_corrigido.startswith(("⚠️","❌")): return "","","" - orig = texto_original.splitlines(); corr = texto_corrigido.splitlines() - diff = difflib.ndiff(orig, corr) - html, crit, rem, add = [], [], 0, 0 - kw_r = ["invenção","identificação","erro","incorreto","prazo"] - kw_a = ["custas","art. 319","art. 337","art. 1022","art. 1015","art. 300","fumus","periculum","essencial","preliminar","tempestividade","cabimento"] - for l in diff: - txt = l[2:]; txt_esc = escape(txt) - if l.startswith(' '): html.append(f"
{txt_esc}
") - elif l.startswith('- '): - rem += 1; html.append(f"
{txt_esc}
") - if txt.strip() and any(kw in txt.lower() for kw in kw_r): crit.append(f"🔴 REM: {txt_esc}") - elif l.startswith('+ '): - add += 1; html.append(f"
{txt_esc}
") - if txt.strip() and any(kw in txt.lower() for kw in kw_a): crit.append(f"🟢 ADD: {txt_esc}") - resumo = f"**Resumo:** Rem={rem}, Add={add}, Saldo={add-rem}" - crit_txt = "### 🚨 Críticas:\n"+"\n".join(crit) if crit else "*Nenhuma crítica.*" - css = "" - html_diff = f"{css}
{''.join(html)}
" - return html_diff, resumo, crit_txt + if not texto_original or not texto_corrigido or texto_corrigido.startswith("⚠️") 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", "fundamento incorreto"] + keywords_criticas_adicionadas = ["custas", "art. 319", "art. 337", "art. 1022", "art. 1015", "art. 300", "fumus", "periculum", "pedido essencial", "preliminar obrigatória"] + + for linha in diff: + texto = linha[2:] + texto_escaped = escape(texto) + if linha.startswith(' '): + resultado_diff_html.append(f"
{texto_escaped}
") + elif linha.startswith('- '): + linhas_removidas += 1 + resultado_diff_html.append(f"
{texto_escaped}
") + if any(kw in texto.lower() for kw in keywords_criticas_removidas): + criticas.append(f"🔴 REMOVIDO (Potencialmente Crítico): {texto_escaped}") + elif linha.startswith('+ '): + linhas_adicionadas += 1 + resultado_diff_html.append(f"
{texto_escaped}
") + if any(kw in texto.lower() for kw in keywords_criticas_adicionadas): + criticas.append(f"🟢 ADICIONADO (Potencialmente Essencial): {texto_escaped}") + + resumo_estatistico = f""" +**Resumo das Mudanças:** +* Linhas Removidas: {linhas_removidas} +* Linhas Adicionadas: {linhas_adicionadas} +* **Diferença Líquida:** {linhas_adicionadas - linhas_removidas} linhas +""" + + criticas_texto = "### 🚨 Mudanças Críticas / Essenciais Identificadas:\n" + "\n".join(criticas) if criticas else "Nenhuma mudança crítica/essencial automaticamente detectada." + + css = """ + +""" + visualizacao_detalhada_html = f"{css}
{''.join(resultado_diff_html)}
" + + return visualizacao_detalhada_html, resumo_estatistico, criticas_texto def calcular_progresso(): - if not HISTORICO: return "📊 Nenhum progresso." - notas, tipos = [], set() + if not HISTORICO: + return "📊 Nenhum progresso registrado ainda." + + notas = [] + tipos_corrigidos = set() for entry in HISTORICO: - tipos.add(entry["tipo_peticao"]) - m = re.search(r"Nota Final:?\s*\**(\d{1,2}[,.]\d{1}|\d{1,2})\s*/\s*10", entry["analise_formatada"], re.I) - if m: - try: notas.append(float(m.group(1).replace(",", "."))) - except ValueError: pass - tot = len(HISTORICO); media = sum(notas)/len(notas) if notas else 0; tend = "" - if len(notas) > 1: tend = "⬆️" if notas[-1] > notas[-2] else ("⬇️" if notas[-1] < notas[-2] else "↔️") - return f"**📊 Resumo:** Corrs:{tot}, Tipos:{len(tipos)}, Média:{media:.1f}/10 ({len(notas)} val), Tend:{tend if tend else 'N/A'}" + tipos_corrigidos.add(entry["tipo_peticao"]) + nota_str = "N/A" + match = re.search(r"\*\*(\d{1,2}[,.]\d{1}|\d{1,2})/10\*\*", entry["analise_formatada"]) + if match: + try: + nota = float(match.group(1).replace(",", ".")) + notas.append(nota) + nota_str = f"{nota:.1f}" + except ValueError: + pass + + total_corridas = len(HISTORICO) + media = sum(notas) / len(notas) if notas else 0 + tendencia = "" + if len(notas) > 1: + if notas[-1] > notas[-2]: + tendencia = "⬆️ Melhorando" + elif notas[-1] < notas[-2]: + tendencia = "⬇️ Piorando" + else: + tendencia = "↔️ Estável" + + progresso_texto = f""" +**📊 Resumo do Progresso:** +* **Total de Correções:** {total_corridas} +* **Tipos de Peças Corrigidas:** {', '.join(sorted(list(tipos_corrigidos)))} +* **Média das Notas (quando aplicável):** {media:.1f}/10 (de {len(notas)} notas válidas) +* **Tendência da Última Nota:** {tendencia if tendencia else 'N/A'} +""" + return progresso_texto def mostrar_historico(): - if not HISTORICO: return "📜 Histórico vazio." - md = "### 📜 Histórico (Últimas 10)\n---\n" + if not HISTORICO: + return "📜 Histórico vazio." + + historico_md = "### 📜 Histórico de Correções (Últimas 10)\n\n---\n" for i, entry in enumerate(reversed(HISTORICO)): - caso = (entry['caso'][:70] + '...') if len(entry['caso']) > 70 else entry['caso'] - nota = "N/A"; m = re.search(r"Nota Final:?\s*\**(\d{1,2}[,.]\d{1}|\d{1,2})\s*/\s*10", entry["analise_formatada"], re.I) - if m: nota = f"{m.group(1).replace(',', '.')}/10" - md += f"**{len(HISTORICO)-i}.** {entry['data']} | **{entry['tipo_peticao']}** | **Nota:{nota}**\n **Caso:** _{caso}_\n---\n" - return md + caso_resumo = (entry['caso'][:80] + '...') if len(entry['caso']) > 80 else entry['caso'] + peticao_resumo = (entry['peticao_original'][:80] + '...') if len(entry['peticao_original']) > 80 else entry['peticao_original'] + + nota_str = "N/A" + match = re.search(r"\*\*(\d{1,2}[,.]\d{1}|\d{1,2})/10\*\*", entry["analise_formatada"]) + if match: + nota_str = f"{match.group(1).replace(',', '.')}/10" + + historico_md += f"**{len(HISTORICO)-i}. Data:** {entry['data']}\n" + historico_md += f"**Tipo:** {entry['tipo_peticao']}\n" + historico_md += f"**Nota:** {nota_str}\n" + historico_md += f"**Caso:** _{caso_resumo}_\n" + historico_md += "---\n" + return historico_md def salvar_progresso(caso, peticao, tipo_peticao): - logger.info("Salvando progresso...") + logger.info("Iniciando salvamento de progresso...") try: - if not caso or not peticao or not tipo_peticao: return "", "⚠️ Inputs necessários.", None - dados = {"tipo_peticao":tipo_peticao,"caso":caso,"peticao":peticao,"timestamp":datetime.now().isoformat()} - json_cont = json.dumps(dados, ensure_ascii=False, indent=4) - fname = f"progresso_peticao_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" - fpath = os.path.join(tempfile.gettempdir(), fname) - with open(fpath, 'w', encoding='utf-8') as f: f.write(json_cont) - logger.info(f"Progresso salvo: {fpath}") - msg = f"✅ Salvo! Baixe `{fname}`." - return json_cont, msg, fpath - except Exception as e: logger.error(f"Erro salvar: {e}", exc_info=True); return "", f"❌ Erro salvar: {e}", None + if not caso or not peticao or not tipo_peticao: + logger.warning("Inputs insuficientes para salvar progresso.") + return "", "⚠️ Caso, Petição e Tipo de Petição são necessários para salvar.", None + + dados_progresso = { + "tipo_peticao": tipo_peticao, + "caso": caso, + "peticao": peticao, + "timestamp": datetime.now().isoformat() + } + + json_content = json.dumps(dados_progresso, ensure_ascii=False, indent=4) + timestamp_str = datetime.now().strftime("%Y%m%d_%H%M%S") + filename = f"progresso_peticao_{timestamp_str}.json" + temp_dir = tempfile.gettempdir() + temp_file_path = os.path.join(temp_dir, filename) + + with open(temp_file_path, 'w', encoding='utf-8') as f: + f.write(json_content) + + logger.info(f"Progresso salvo com sucesso no arquivo temporário: {temp_file_path}") + success_message = ( + f"✅ Progresso salvo! Baixe o arquivo `{filename}`. " + "Use o botão 'Carregar Progresso' e selecione este arquivo para restaurar." + ) + return json_content, success_message, temp_file_path + + except Exception as e: + logger.error(f"Erro ao salvar progresso: {str(e)}", exc_info=True) + return "", f"❌ Erro ao salvar o progresso: {str(e)}", None def carregar_progresso(arquivo_entrada): - logger.info("Carregando progresso...") - if arquivo_entrada is None: return "", "", "", "⚠️ Nenhum arquivo." + logger.info("Iniciando carregamento de progresso...") + if arquivo_entrada is None: + return "", "", "", "⚠️ Nenhum arquivo selecionado para carregar." + try: - fpath = arquivo_entrada.name; logger.info(f"Carregando: {fpath}") - with open(fpath, 'r', encoding='utf-8') as f: dados = json.load(f) - caso, peticao, tipo = dados.get("caso"), dados.get("peticao"), dados.get("tipo_peticao") - if not all([caso, peticao, tipo]): return "", "", "", "❌ Arquivo inválido." - if tipo not in TIPOS_DE_PETICAO: return caso, peticao, "", f"⚠️ Tipo '{tipo}' inválido." - logger.info("Progresso carregado."); return tipo, caso, peticao, "✅ Progresso carregado!" - except Exception as e: logger.error(f"Erro carregar: {e}", exc_info=True); return "", "", "", f"❌ Erro carregar: {e}" + file_path = arquivo_entrada.name + logger.info(f"Carregando progresso do arquivo: {file_path}") + + with open(file_path, 'r', encoding='utf-8') as f: + dados = json.load(f) + + caso = dados.get("caso") + peticao = dados.get("peticao") + tipo_peticao = dados.get("tipo_peticao") + + if caso is None or peticao is None or tipo_peticao is None: + logger.warning(f"Arquivo JSON carregado não contém as chaves esperadas: {dados.keys()}") + return "", "", "", "❌ Erro: Arquivo de progresso inválido ou incompleto." + + if tipo_peticao not in TIPOS_DE_PETICAO: + logger.warning(f"Tipo de petição '{tipo_peticao}' do arquivo não é válido.") + return caso, peticao, "", f"⚠️ Tipo de petição '{tipo_peticao}' do arquivo não é mais válido. Selecione manualmente." + + logger.info("Progresso carregado com sucesso.") + return tipo_peticao, caso, peticao, "✅ Progresso carregado com sucesso!" + + except json.JSONDecodeError: + logger.error(f"Erro ao decodificar JSON do arquivo: {file_path}") + return "", "", "", "❌ Erro: Arquivo selecionado não é um JSON válido." + except Exception as e: + logger.error(f"Erro ao carregar o progresso: {str(e)}", exc_info=True) + return "", "", "", f"❌ Erro inesperado ao carregar o progresso: {str(e)}" def estruturar_prompt_geracao_caso(tipo_peticao="Petição Inicial"): - prompt = f"Crie caso fictício **detalhado e realista** p/ peça **'{tipo_peticao}'** (simulado OAB).\n**Geral:**\n1.Complexidade adequada (15-25l).\n2.Dados Essenciais (nomes,locais,datas,valores,relação,conflito).\n3.Coerência.\n4.Foco no Tipo Peça.\n**Específico '{tipo_peticao}':**\n" - if tipo_peticao == "Petição Inicial": prompt += "* Descreva violação. * Inclua danos. * Dados qualificação. * Tema: Consumo, Contrato, etc." - elif tipo_peticao == "Contestação": prompt += "* Resumo PI recebida. * Versão Réu (p/ prelim, impug, mérito). * Dados qualif réu." - elif tipo_peticao == "Reconvenção": prompt += "* Estrutura Contestação + Fatos/Fundamentos p/ pedido PRÓPRIO CONEXO do Réu contra Autor." - elif tipo_peticao == "Impugnação à Contestação": prompt += "* Resumo PI Autor. * Resumo Contestação Réu (prelim, fatos novos, docs). * Objetivo: Rebater." - elif tipo_peticao == "Embargos de Declaração": prompt += "* Breve caso. * TRECHO DECISÃO. * INDIQUE VÍCIO (omissão/contradição/obscuridade/erro material)." - elif tipo_peticao == "Recurso de Apelação": prompt += "* Caso original. * Resumo SENTENÇA. * ELEMENTOS ERRO (in judicando/procedendo). * Indique apelante." - elif tipo_peticao == "Agravo de Instrumento": prompt += "* Caso andamento. * DECISÃO INTERLOCUTÓRIA. * Enquadrar Art.1015/mitig. * ELEMENTOS ERRO + URGÊNCIA. * Indique agravante." - elif tipo_peticao == "Petição de Tutela Provisória": prompt += "* Situação URGENTE ou EVIDENTE. * ELEMENTOS: Urg=Fumus+Peric; Evid=Hipót Art.311. * Medida específica. * Antec/Incid." - else: prompt += "* Crie cenário cível com conflito." - prompt += f'\n**Finalize:**\n"**Situação-Problema:** Advogado(a) de [Nome Cliente], elabore a peça cabível ({tipo_peticao})."\n**Importante:** Temas comuns.' - return prompt + prompt = f""" + Crie um caso fictício **detalhado e realista** para um exercício prático de elaboração de uma peça do tipo **"{tipo_peticao}"**, simulando um cenário de Exame de Ordem (OAB). -def gerar_caso_ficticio(tipo_peticao="Petição Inicial"): - logger.info(f"Gerando caso para: {tipo_peticao}") - try: - if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO: return "⚠️ Selecione tipo válido." - prompt = estruturar_prompt_geracao_caso(tipo_peticao) - caso_gerado = chamar_api(prompt) - if caso_gerado.startswith(("❌","⏱️","⚠️")): return f"⚠️ Falha gerar caso:\n{caso_gerado}" - frase = "**Situação-Problema:** Na qualidade de advogado(a) de" - if frase.lower() not in caso_gerado.lower(): - logger.warning("Frase final não encontrada. Adicionando.") - nomes = re.findall(r"[A-Z][a-z]+(?:\s[A-Z][a-z]+)+", caso_gerado) - cliente = nomes[0] if nomes else "[Cliente]" - caso_gerado += f"\n\n**Situação-Problema:** Na qualidade de advogado(a) de {cliente}, elabore a peça processual cabível ({tipo_peticao})." - logger.info("Caso gerado.") - return caso_gerado - except Exception as e: logger.exception("Erro gerar_caso:"); return f"❌ Erro gerar caso: {e}" - -def atualizar_config(temp, max_tokens): - CONFIG["temperatura"] = temp - CONFIG["max_tokens"] = max_tokens - logger.info(f"Configs: Temp={temp}, Max Tokens={max_tokens}") - return f"✅ Configs Atualizadas!" + **Instruções Gerais para o Caso:** + 1. **Complexidade Adequada:** Deve ter informações suficientes para redigir a peça solicitada, mas sem ser excessivamente longo ou complicado. (Ideal: 15-25 linhas). + 2. **Dados Essenciais:** Inclua nomes completos (fictícios) das partes envolvidas, locais (cidades/estados fictícios ou genéricos), datas relevantes, valores monetários (se aplicável), e uma descrição clara da relação jurídica e do conflito. + 3. **Coerência:** Os fatos devem ser lógicos e justificar a peça processual indicada. + 4. **Foco no Tipo de Peça:** O cenário deve ser construído para exigir especificamente a elaboração de um(a) **{tipo_peticao}**. + + **Instruções Específicas para Gerar Caso para "{tipo_peticao}":** + """ + if tipo_peticao == "Petição Inicial": + prompt += """ + * Descreva uma situação onde direitos foram violados (contrato descumprido, dano sofrido, etc.) e o cliente (autor) busca a tutela jurisdicional pela primeira vez. + * Inclua elementos que permitam calcular danos materiais e/ou justificar danos morais. + * Forneça dados para qualificação das partes (autor e réu). + * **Exemplo de Tema:** Acidente de consumo, inadimplemento contratual, cobrança indevida, ação de despejo, etc. + """ + elif tipo_peticao == "Contestação": + prompt += """ + * **Apresente PRIMEIRO um resumo da Petição Inicial** que o réu (seu cliente) recebeu (quem processou, o que alegou, o que pediu). + * **Depois, apresente a versão dos fatos do RÉU**, incluindo informações que permitam: + * Alegar preliminares (ex: incompetência do juízo, ilegitimidade da parte, etc.). + * Impugnar os fatos da inicial. + * Apresentar defesa de mérito (ex: pagamento já realizado, culpa exclusiva da vítima, prescrição, etc.). + * Forneça dados para qualificação do réu. + """ + elif tipo_peticao == "Reconvenção": + prompt += """ + * Siga a estrutura da **Contestação**: Apresente o resumo da Petição Inicial do autor original. + * Apresente a versão dos fatos do Réu (seu cliente) para a defesa. + * **ADICIONE fatos e fundamentos que permitam ao RÉU fazer um pedido PRÓPRIO contra o AUTOR ORIGINAL**, que seja CONEXO com a ação principal ou com a defesa. + * **Exemplo:** Numa ação de cobrança, o réu contesta e reconvém pedindo indenização por danos que o autor lhe causou no mesmo negócio. + """ + elif tipo_peticao == "Impugnação à Contestação": + prompt += """ + * Apresente um resumo da Petição Inicial original do seu cliente (Autor). + * **Apresente um resumo da Contestação** apresentada pelo réu, destacando: + * As preliminares arguidas pelo réu (se houver). + * Os fatos novos (impeditivos, modificativos, extintivos) alegados pelo réu. + * Os documentos juntados pelo réu (se houver menção a algum relevante). + * O objetivo é fornecer elementos para o Autor rebater a contestação. + """ + elif tipo_peticao == "Embargos de Declaração": + prompt += """ + * Descreva brevemente o caso original (quem processou quem e por quê). + * **Apresente o DISPOSITIVO ou um TRECHO FUNDAMENTAL de uma DECISÃO JUDICIAL (sentença, acórdão, etc.)** proferida no caso. + * **INDIQUE CLARAMENTE qual o vício (omissão, contradição, obscuridade ou erro material) presente NESSA DECISÃO**, fornecendo contexto para que o aluno possa identificar e fundamentar os Embargos. + * **Exemplo:** "A sentença julgou procedente o pedido X, mas foi omissa quanto ao pedido Y que também constava na inicial." OU "O acórdão afirmou X na fundamentação, mas concluiu Y no dispositivo, gerando contradição." + """ + elif tipo_peticao == "Recurso de Apelação": + prompt += """ + * Descreva o caso original (Autor vs. Réu, causa de pedir, pedidos). + * **Apresente um resumo da SENTENÇA proferida pelo juiz de primeira instância**, indicando se foi procedente, improcedente, parcialmente procedente, e os principais fundamentos do juiz. + * **FORNEÇA ELEMENTOS que indiquem um ERRO na sentença (error in judicando ou in procedendo)**, que justifiquem a reforma ou anulação pelo Tribunal. + * **Exemplo:** "A sentença julgou improcedente o pedido por falta de provas, ignorando o documento X juntado aos autos." OU "O juiz indeferiu a oitiva da testemunha chave Y, cerceando a defesa do réu, e julgou procedente." + * Indique quem é o cliente (apelante). + """ + elif tipo_peticao == "Agravo de Instrumento": + prompt += """ + * Descreva o caso original em andamento. + * **Apresente uma DECISÃO INTERLOCUTÓRIA específica** proferida pelo juiz (NÃO a sentença final). + * **A decisão DEVE se enquadrar numa das hipóteses do Art. 1015 do CPC** (ou justificar a taxatividade mitigada). + * **FORNEÇA ELEMENTOS que indiquem um ERRO nessa decisão interlocutória** E, se possível, a **URGÊNCIA** em revertê-la (risco de dano). + * **Exemplo:** "O juiz indeferiu o pedido de gratuidade de justiça, embora o autor tenha comprovado baixa renda." OU "O juiz deferiu a liminar de busca e apreensão do bem X sem a devida comprovação da mora." + * Indique quem é o cliente (agravante). + """ + elif tipo_peticao == "Petição de Tutela Provisória": + prompt += """ + * Descreva uma situação de conflito onde uma das partes precisa de uma medida **urgente** (risco iminente) ou onde seu direito é **evidente**. + * **FORNEÇA ELEMENTOS claros que demonstrem:** + * **Para Urgência:** A probabilidade do direito (fumus boni iuris) E o perigo de dano ou risco ao resultado útil do processo (periculum in mora). + * **Para Evidência:** Uma das hipóteses do Art. 311 do CPC (ex: defesa protelatória, tese firmada em repetitivo, etc.). + * Indique qual medida específica o cliente deseja (ex: bloqueio de bens, retirada de nome do SPC, etc.). + * Especifique se a tutela é pedida ANTES do processo principal (antecedente) ou DENTRO de um processo já existente (incidental). + """ + else: + prompt += "* Crie um cenário jurídico cível com um conflito claro entre duas ou mais partes." + + prompt += """ + **Finalize o caso com:** + "**Situação-Problema:** Na qualidade de advogado(a) de [Nome do Cliente], elabore a peça processual cabível ({tipo_peticao})." + + **Importante:** Evite criar casos que dependam de conhecimento jurídico EXTREMAMENTE específico ou de nicho. Foque em temas comuns do direito civil e processual civil. Seja criativo! + """ + return prompt def interface_gradio(): with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="orange")) as demo: - gr.Markdown("# ⚖️ Corretor Jurídico OAB Master ⚖️\nAnalise e aprimore suas peças processuais!") + gr.Markdown("# ⚖️ Corretor Jurídico OAB Master ⚖️") + gr.Markdown("Analise e aprimore suas peças processuais! Selecione o tipo, forneça o caso e sua peça (texto ou arquivo).") + with gr.Row(): with gr.Column(scale=1): - gr.Markdown("### ⚙️ Configs") - tipo_peticao_input = gr.Dropdown(label="1. Tipo Peça", choices=TIPOS_DE_PETICAO, value="Petição Inicial") - modo_treinamento = gr.Checkbox(label="🎓 Treinamento", value=False) - with gr.Accordion("🛠️ Opções IA", open=False): - temperatura_slider = gr.Slider(0.0, 1.0, value=CONFIG["temperatura"], step=0.1, label="Criatividade") - max_tokens_slider = gr.Slider(2048, 8192, value=CONFIG["max_tokens"], step=256, label="Max Tokens") - config_update_btn = gr.Button("💾 Salvar Configs") - config_status = gr.Textbox(label="Status", interactive=False, lines=1) - with gr.Accordion("📊 Dashboard", open=False): - progresso_output = gr.Markdown("*Atualize...*") - progresso_btn = gr.Button("🔄 Progresso") - with gr.Accordion("📜 Histórico", open=False): - historico_output = gr.Markdown("*Atualize...*") - historico_btn = gr.Button("🔄 Histórico") - with gr.Accordion("💾📂 Salvar/Carregar", open=False): - salvar_btn = gr.Button("💾 Salvar") - carregar_btn = gr.Button("📂 Carregar") - arquivo_progresso_upload = gr.File(label=".json", file_types=[".json"]) - progresso_download_file = gr.File(label="Baixar", interactive=False) + gr.Markdown("### Configurações") + tipo_peticao_input = gr.Dropdown( + label="Selecione o Tipo de Peça Processual", + choices=TIPOS_DE_PETICAO, + value="Petição Inicial" + ) + modo_treinamento = gr.Checkbox(label="🎓 Modo Treinamento (Feedback Detalhado)", value=False) + + with gr.Accordion("⚙️ Configurações Avançadas", open=False): + temperatura_slider = gr.Slider(minimum=0.0, maximum=1.0, value=CONFIG["temperatura"], step=0.1, label="Criatividade (Temperatura)") + timeout_slider = gr.Slider(minimum=30, maximum=180, value=CONFIG["timeout"], step=10, label="Timeout da API (segundos)") + max_tokens_slider = gr.Slider(minimum=2048, maximum=8192, value=CONFIG["max_tokens"], step=256, label="Tamanho Máx. Resposta (Tokens)") + def atualizar_config(temp, timeout, tokens): + CONFIG["temperatura"] = temp + CONFIG["timeout"] = timeout + CONFIG["max_tokens"] = tokens + logger.info(f"Configurações atualizadas: Temp={temp}, Timeout={timeout}, Tokens={tokens}") + return f"✅ Configs Atualizadas!" + config_update_btn = gr.Button("💾 Salvar Configs da Sessão") + config_status = gr.Textbox(label="Status Config", interactive=False) + config_update_btn.click(atualizar_config, inputs=[temperatura_slider, timeout_slider, max_tokens_slider], outputs=config_status) + + with gr.Accordion("📊 Dashboard de Progresso", open=False): + progresso_output = gr.Markdown("Clique em 'Atualizar Progresso' para ver suas estatísticas.") + progresso_btn = gr.Button("🔄 Atualizar Progresso") + + with gr.Accordion("📜 Histórico da Sessão", open=False): + historico_output = gr.Markdown("Clique em 'Ver Histórico' para ver suas últimas correções.") + historico_btn = gr.Button("🔄 Ver Histórico") + + with gr.Accordion("💾/📂 Salvar/Carregar Progresso Atual", open=False): + salvar_btn = gr.Button("💾 Salvar Caso/Peça Atual") + carregar_btn = gr.Button("📂 Carregar Caso/Peça de Arquivo") + arquivo_progresso_upload = gr.File(label="Selecione o arquivo .json para carregar", file_types=[".json"], scale=2) + progresso_download_file = gr.File(label="Baixar Progresso Salvo", interactive=False) + with gr.Column(scale=3): - gr.Markdown("### 1. Caso / Contexto") + gr.Markdown("### 1. Forneça o Caso / Contexto") with gr.Row(): - caso_input = gr.Textbox(label="📋 Caso", lines=8, placeholder="Insira o texto do caso...") - caso_btn = gr.Button("🎲 Gerar", variant="secondary", scale=0) - gr.Markdown("### 2. Sua Peça") + caso_input = gr.Textbox(label="📋 Caso ou Situação Processual", lines=10, placeholder="Insira o texto COMPLETO do caso ou da situação (ex: resumo da inicial para contestação, decisão para embargos/recurso).") + caso_btn = gr.Button("🎲 Gerar Caso Fictício", variant="secondary", scale=0) + + gr.Markdown("### 2. Insira Sua Peça Processual") with gr.Tabs(): - with gr.TabItem("✏️ Texto"): - peticao_texto_input = gr.Textbox(label="Texto", lines=12, placeholder="Cole ou digite...") - dica_contextual_output = gr.Markdown("*Dicas...*") - btn_corrigir = gr.Button("🚀 CORRIGIR!", variant="primary") - with gr.TabItem("📎 Arquivo"): - peticao_arquivo_input = gr.File(label="Arquivo (.pdf,.docx,.txt)", file_types=[".pdf",".docx",".txt"]) - arquivo_preview_output = gr.Textbox(label="🔍 Prévia", lines=4, interactive=False) - btn_corrigir_arquivo = gr.Button("🚀 CORRIGIR ARQUIVO!", variant="primary") + with gr.TabItem("✏️ Digitar Texto"): + peticao_texto_input = gr.Textbox(label="Texto da Peça", lines=15, placeholder="Cole ou digite sua peça processual aqui...") + dica_contextual_output = gr.Markdown(value="*Dicas aparecerão aqui enquanto você digita...*") + btn_corrigir = gr.Button("🚀 CORRIGIR E ANALISAR!", variant="primary") + + with gr.TabItem("📎 Anexar Arquivo"): + peticao_arquivo_input = gr.File(label="Arquivo da Peça (.pdf, .docx, .txt - Máx 15MB)", file_types=[".pdf", ".docx", ".txt"]) + arquivo_preview_output = gr.Textbox(label="🔍 Prévia do Conteúdo do Arquivo (Texto Extraído)", lines=5, interactive=False) + btn_corrigir_arquivo = gr.Button("🚀 CORRIGIR E ANALISAR ARQUIVO!", variant="primary") processing_message = gr.Markdown("", visible=False) - gr.Markdown("--- \n ### 3. Resultados") + with gr.Tabs(): with gr.TabItem("📊 Análise e Correção"): - resultado_analise_output = gr.Markdown(label="Análise e Nota") - resultado_peticao_corrigida_output = gr.Textbox(label="✨ Versão Sugerida", lines=18, show_copy_button=True) - with gr.TabItem("⚖️ Comparativo"): - gr.Markdown("### ↔️ Comparativo (Diff)") + gr.Markdown("### 3. Resultados da Análise") + resultado_analise_output = gr.Markdown(label="Análise Detalhada e Nota") + + gr.Markdown("### ✨ Versão Sugerida (Corrigida/Aprimorada)") + resultado_peticao_corrigida_output = gr.Textbox(label="Peça Processual Corrigida", lines=20, show_copy_button=True) + + with gr.TabItem("⚖️ Comparativo (Diff)"): + gr.Markdown("### ↔️ Comparativo: Sua Peça vs. Versão Sugerida") with gr.Row(): - with gr.Column(scale=1): resumo_alteracoes_output = gr.Markdown(label="Resumo"); criticas_output = gr.Markdown(label="Críticas") - with gr.Column(scale=3): visualizacao_detalhada_output = gr.HTML(label="Detalhes") + with gr.Column(scale=1): + resumo_alteracoes_output = gr.Markdown(label="Resumo das Mudanças") + criticas_output = gr.Markdown(label="Mudanças Críticas/Essenciais") + with gr.Column(scale=3): + visualizacao_detalhada_output = gr.HTML(label="📝 Alterações Detalhadas (Diff)") + clear_btn = gr.Button("🗑️ Limpar Tudo") - caso_btn.click(lambda: gr.update(value="⏳ Gerando...", visible=True), outputs=processing_message).then( - fn=gerar_caso_ficticio, inputs=[tipo_peticao_input], outputs=[caso_input] - ).then(lambda: gr.update(value="", visible=False), outputs=processing_message) + caso_btn.click( + fn=gerar_caso_ficticio, + inputs=[tipo_peticao_input], + outputs=[caso_input] + ) + + peticao_texto_input.change( + fn=fornecer_dica_contextual, + inputs=[peticao_texto_input, tipo_peticao_input], + outputs=dica_contextual_output + ) + tipo_peticao_input.change( + fn=fornecer_dica_contextual, + inputs=[peticao_texto_input, tipo_peticao_input], + outputs=dica_contextual_output + ) - peticao_texto_input.change(fornecer_dica_contextual, [peticao_texto_input, tipo_peticao_input], dica_contextual_output) - tipo_peticao_input.change(fornecer_dica_contextual, [peticao_texto_input, tipo_peticao_input], dica_contextual_output) - peticao_arquivo_input.change(process_file, [peticao_arquivo_input], [arquivo_preview_output]) + peticao_arquivo_input.change( + fn=process_file, + inputs=[peticao_arquivo_input], + outputs=[arquivo_preview_output] + ) - btn_corrigir.click(lambda: gr.update(value="⏳ Processando texto...", visible=True), outputs=processing_message).then( + btn_corrigir.click( + fn=lambda: gr.update(value="⏳ Processando... Gerando análise e correção. Isso pode levar um minuto...", visible=True), + inputs=None, + outputs=[processing_message] + ).then( fn=corrigir_peticao, inputs=[caso_input, tipo_peticao_input, peticao_texto_input, gr.State(None), modo_treinamento], - outputs=[resultado_analise_output, resultado_peticao_corrigida_output, resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output], - concurrency_limit=1 - ).then(lambda: gr.update(value="", visible=False), outputs=processing_message) + outputs=[resultado_analise_output, gr.Textbox(visible=False), resultado_peticao_corrigida_output, + resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output] + ).then( + fn=lambda: gr.update(value="", visible=False), + inputs=None, + outputs=[processing_message] + ) - btn_corrigir_arquivo.click(lambda: gr.update(value="⏳ Processando arquivo...", visible=True), outputs=processing_message).then( + btn_corrigir_arquivo.click( + fn=lambda: gr.update(value="⏳ Processando arquivo... Gerando análise e correção. Isso pode levar um minuto...", visible=True), + inputs=None, + outputs=[processing_message] + ).then( fn=corrigir_peticao, inputs=[caso_input, tipo_peticao_input, gr.State(None), peticao_arquivo_input, modo_treinamento], - outputs=[resultado_analise_output, resultado_peticao_corrigida_output, resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output], - concurrency_limit=1 - ).then(lambda: gr.update(value="", visible=False), outputs=processing_message) + outputs=[resultado_analise_output, gr.Textbox(visible=False), resultado_peticao_corrigida_output, + resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output] + ).then( + fn=lambda: gr.update(value="", visible=False), + inputs=None, + outputs=[processing_message] + ) - config_update_btn.click(atualizar_config, [temperatura_slider, max_tokens_slider], config_status) - progresso_btn.click(calcular_progresso, outputs=progresso_output) - historico_btn.click(mostrar_historico, outputs=historico_output) + clear_btn.click( + fn=lambda: [ + "Petição Inicial", False, None, None, None, None, "", "", "", "", "", "", "", None, None, + ], + inputs=None, + outputs=[ + tipo_peticao_input, modo_treinamento, caso_input, peticao_texto_input, peticao_arquivo_input, + arquivo_preview_output, processing_message, resultado_analise_output, + resultado_peticao_corrigida_output, resumo_alteracoes_output, criticas_output, + visualizacao_detalhada_output, dica_contextual_output, arquivo_progresso_upload, + progresso_download_file + ] + ) - salvar_btn.click(lambda: gr.update(value="⏳ Salvando...", visible=True), outputs=processing_message).then( - fn=salvar_progresso, inputs=[caso_input, peticao_texto_input, tipo_peticao_input], + progresso_btn.click(fn=calcular_progresso, outputs=progresso_output) + historico_btn.click(fn=mostrar_historico, outputs=historico_output) + + salvar_btn.click( + fn=lambda: gr.update(value="⏳ Salvando progresso atual...", visible=True), + inputs=None, outputs=processing_message + ).then( + fn=salvar_progresso, + inputs=[caso_input, peticao_texto_input, tipo_peticao_input], outputs=[gr.Textbox(visible=False), config_status, progresso_download_file] - ).then(lambda x: gr.update(visible=bool(x)), [progresso_download_file], [progresso_download_file] - ).then(lambda: gr.update(value="", visible=False), None, processing_message) + ).then( + fn=lambda x: gr.update(visible=bool(x)), + inputs=[progresso_download_file], + outputs=[progresso_download_file] + ).then( + lambda: gr.update(value="", visible=False), None, processing_message + ) - carregar_btn.click(lambda: gr.update(value="⏳ Carregando...", visible=True), outputs=processing_message).then( - fn=carregar_progresso, inputs=[arquivo_progresso_upload], + carregar_btn.click( + fn=lambda: gr.update(value="⏳ Carregando progresso do arquivo...", visible=True), + inputs=None, outputs=processing_message + ).then( + fn=carregar_progresso, + inputs=[arquivo_progresso_upload], outputs=[tipo_peticao_input, caso_input, peticao_texto_input, config_status] - ).then(lambda: gr.update(value="", visible=False), None, processing_message) - - clear_btn.click( - fn=lambda: ["Petição Inicial", False, None, None, None, None, "*Dicas...*", "", "", "", "", "", None, None, ""], - outputs=[tipo_peticao_input, modo_treinamento, caso_input, peticao_texto_input, peticao_arquivo_input, - arquivo_preview_output, dica_contextual_output, resultado_analise_output, resultado_peticao_corrigida_output, - resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output, - arquivo_progresso_upload, progresso_download_file, config_status] + ).then( + lambda: gr.update(value="", visible=False), None, processing_message ) + return demo +def validar_peticao_basica(peticao): + problemas = [] + peticao_lower = peticao.lower() + if "excelentíssimo" not in peticao_lower and "eméritos" not in peticao_lower and "meritíssimo" not in peticao_lower : + problemas.append("Endereçamento inicial parece ausente ou incompleto.") + if "valor da causa" not in peticao_lower and "valor à causa" not in peticao_lower: + problemas.append("Atribuição de Valor da Causa não encontrada (essencial na Inicial).") + if "juntada da guia de custas" not in peticao_lower and "gratuidade de justiça" not in peticao_lower : + problemas.append("Pedido de juntada de custas ou gratuidade de justiça não encontrado (essencial na Inicial).") + if "nestes termos" not in peticao_lower and "pede deferimento" not in peticao_lower: + problemas.append("Fechamento formal padrão ('Nestes termos pede deferimento') parece ausente.") + return problemas + +def gerar_caso_ficticio(tipo_peticao="Petição Inicial"): + if not tipo_peticao: + return "Erro: Tipo de petição não selecionado para gerar caso." + logger.info(f"Gerando caso fictício para: {tipo_peticao}") + prompt = estruturar_prompt_geracao_caso(tipo_peticao) + caso_gerado = chamar_api(prompt) + + if caso_gerado.startswith("❌") or caso_gerado.startswith("⏱️") or caso_gerado.startswith("⚠️"): + logger.error(f"Erro ao gerar caso fictício: {caso_gerado}") + return f"Falha ao gerar caso fictício.\n{caso_gerado}" + + frase_final_padrao = f"**Situação-Problema:** Na qualidade de advogado(a) de" + if frase_final_padrao.lower() not in caso_gerado.lower(): + logger.warning("Frase 'Situação-Problema' não encontrada na geração do caso. Adicionando manualmente.") + nomes_possiveis = re.findall(r"[A-Z][a-z]+(?:\s[A-Z][a-z]+)+", caso_gerado) + cliente_nome = nomes_possiveis[0] if nomes_possiveis else "[Cliente]" + caso_gerado += f"\n\n**Situação-Problema:** Na qualidade de advogado(a) de {cliente_nome}, elabore a peça processual cabível ({tipo_peticao})." + + logger.info("Caso fictício gerado com sucesso.") + return caso_gerado + if __name__ == "__main__": - logger.info("Iniciando a aplicação Gradio...") + # A configuração do Tesseract agora depende do packages.txt no Hugging Face + # Não é necessário configurar o caminho no código Python aqui. app = interface_gradio() - app.launch() - logger.info("Aplicação Gradio encerrada.") \ No newline at end of file + app.launch() \ No newline at end of file