| import gradio as gr |
| 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 |
| 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 re |
| from fpdf import FPDF |
| import google.generativeai as genai |
| import google.api_core.exceptions |
|
|
| 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") |
| if not GEMINI_API_KEY: |
| raise ValueError("❌ A chave GEMINI_API_KEY não foi encontrada. Configure-a no arquivo .env ou como Secret.") |
|
|
| genai.configure(api_key=GEMINI_API_KEY) |
| MODEL_NAME = "gemini-2.0-flash" |
|
|
| CONFIG = { |
| "max_retry_attempts": 3, |
| "retry_delay": 5, |
| "timeout": 180, |
| "temperatura": 0.3, |
| "max_tokens": 8192 |
| } |
|
|
| CONFIG_OAB = { |
| "temperatura": 0.2, |
| "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", |
| "Ação de Alimentos Gravídicos", |
| "Reclamação Trabalhista", |
| "Contestação Trabalhista", |
| "Recurso Ordinário", |
| "Mandado de Segurança", |
| "Habeas Data", |
| "Ação Popular", |
| ] |
|
|
| DICAS_OAB_MELHORADO = { |
| "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).", |
| "Reclamação Trabalhista (Endereçamento)": "Enderece à Vara do Trabalho competente. A competência territorial é, em regra, o local da prestação de serviços (Art. 651, CLT).", |
| "Reclamação Trabalhista (Pedidos)": "Os pedidos devem ser líquidos (com valor atribuído) nos ritos sumaríssimo e ordinário, conforme Art. 840, §1º, da CLT.", |
| "Recurso Ordinário (Pressupostos)": "Além dos pressupostos comuns, atente-se ao depósito recursal e ao recolhimento das custas processuais (Art. 899, CLT).", |
| "Mandado de Segurança (Fundamentação)": "É crucial demonstrar o 'direito líquido e certo', o 'ato coator' ilegal ou abusivo e a 'autoridade coatora'. Fundamente na Lei 12.016/09.", |
| "Mandado de Segurança (Cabimento)": "Lembre-se: não cabe MS quando houver recurso específico com efeito suspensivo ou contra lei em tese (Súmulas STF/STJ).", |
| } |
|
|
| HISTORICO = [] |
| CORRECAO_CACHE = {} |
|
|
| def preprocess_image_for_ocr(img): |
| try: |
| 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 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 = "" |
| full_text = "" |
| try: |
| pdf_reader = pdfplumber_open(file_path_or_obj) |
| for i, page in enumerate(pdf_reader.pages): |
| page_full_text = "" |
| page_text = page.extract_text() |
| if page_text: |
| page_full_text += page_text + "\n" |
| text += page_text + "\n" |
| else: |
| logger.warning(f"Página {i+1} sem texto extraível, tentando OCR.") |
| try: |
| if 'pytesseract' in globals(): |
| 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(): |
| page_full_text += page_ocr_text + "\n" |
| ocr_text += page_ocr_text + "\n" |
| else: |
| logger.warning(f"OCR falhou na página {i+1}.") |
| page_full_text += f"[OCR falhou na página {i+1} - Tesseract instalado?]\n" |
| ocr_text += f"[OCR falhou na página {i+1}]\n" |
| else: |
| logger.warning(f"OCR não tentado na página {i+1} - biblioteca pytesseract não encontrada.") |
| page_full_text += f"[OCR não disponível na página {i+1}]\n" |
| ocr_text += f"[OCR não disponível 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)}") |
| page_full_text += f"[Erro no OCR da página {i+1}: {str(ocr_e)}]\n" |
| ocr_text += f"[Erro no OCR da página {i+1}: {str(ocr_e)}]\n" |
| full_text += page_full_text |
| pdf_reader.close() |
|
|
| if full_text.strip(): |
| logger.info("Texto combinado (extração/OCR) obtido do PDF.") |
| return full_text |
| else: |
| logger.warning("Não foi possível extrair texto significativo do PDF (nem extração, nem OCR).") |
| 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 = "" |
| reader = None |
| if isinstance(file_path_or_obj, str): |
| with open(file_path_or_obj, 'rb') as f: |
| try: |
| reader = PyPDF2.PdfReader(f) |
| except PyPDF2.errors.PdfReadError as pdf_err: |
| logger.error(f"Erro PyPDF2 ao ler {file_path_or_obj}: {pdf_err}") |
| return f"❌ Erro ao ler PDF (PyPDF2): {pdf_err}" |
| if reader: |
| for page in reader.pages: text_fallback += page.extract_text() or "" |
| elif hasattr(file_path_or_obj, 'seek') and hasattr(file_path_or_obj, 'read'): |
| file_path_or_obj.seek(0) |
| try: |
| reader = PyPDF2.PdfReader(file_path_or_obj) |
| except PyPDF2.errors.PdfReadError as pdf_err: |
| logger.error(f"Erro PyPDF2 ao ler objeto stream: {pdf_err}") |
| return f"❌ Erro ao ler PDF (PyPDF2 Stream): {pdf_err}" |
| if reader: |
| for page in reader.pages: text_fallback += page.extract_text() or "" |
| else: |
| logger.error("Tipo de entrada PDF inválido para PyPDF2.") |
| return f"❌ Erro interno: Tipo de entrada PDF inválido para fallback." |
|
|
|
|
| if text_fallback.strip(): |
| logger.info("Texto extraído com fallback PyPDF2.") |
| return text_fallback |
| else: |
| return f"❌ Erro ao processar o PDF: {str(e)} (Fallback PyPDF2 também não extraiu texto)." |
| 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)}. Erro fallback: {str(fallback_e)}" |
|
|
| def extract_text_from_docx(file_path_or_obj): |
| try: |
| doc = docx.Document(file_path_or_obj) |
| text = "\n".join([para.text for para in doc.paragraphs]) |
| 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 = None |
| if isinstance(file_path_or_obj, str): |
| with open(file_path_or_obj, 'rb') as f: |
| content = f.read() |
| elif hasattr(file_path_or_obj, 'read'): |
| if hasattr(file_path_or_obj, 'seek'): |
| try: file_path_or_obj.seek(0) |
| except io.UnsupportedOperation: pass |
| content = file_path_or_obj.read() |
| if hasattr(file_path_or_obj, 'seek'): |
| try: file_path_or_obj.seek(0) |
| except io.UnsupportedOperation: pass |
| else: |
| return "❌ Tipo de entrada TXT inválido." |
|
|
| if content is None: |
| return "❌ Erro ao ler conteúdo TXT." |
|
|
| encodings = ['utf-8', 'latin-1', 'cp1252', 'iso-8859-1'] |
| for encoding in encodings: |
| try: |
| decoded_text = content.decode(encoding) |
| logger.info(f"Arquivo TXT decodificado com {encoding}.") |
| return decoded_text if decoded_text.strip() else "⚠️ Arquivo TXT vazio." |
| except UnicodeDecodeError: |
| continue |
| logger.error("Não foi possível decodificar o arquivo TXT com encodings comuns.") |
| try: |
| decoded_text_replace = content.decode('utf-8', errors='replace') |
| logger.warning("Decodificado TXT com UTF-8 e 'replace'. Pode haver caracteres substituídos.") |
| return decoded_text_replace if decoded_text_replace.strip() else "⚠️ Arquivo TXT vazio." |
| except Exception: |
| 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: |
| logger.warning("Nenhum arquivo fornecido para processamento.") |
| return "⚠️ Nenhum arquivo enviado." |
| try: |
| file_path = getattr(file_obj, 'name', None) |
| original_filename = getattr(file_obj, 'orig_name', file_path) |
|
|
| if not file_path or not original_filename: |
| logger.error("Não foi possível obter o nome/caminho do arquivo do objeto.") |
| return "❌ Erro: Não foi possível identificar o tipo do arquivo." |
|
|
| filename_lower = os.path.basename(original_filename).lower() |
| logger.info(f"Processando arquivo: {original_filename} (path: {file_path})") |
|
|
| try: |
| file_size = os.path.getsize(file_path) |
| logger.info(f"Tamanho do arquivo: {file_size} bytes") |
| if file_size > 15 * 1024 * 1024: |
| logger.warning(f"Arquivo {original_filename} excede o limite de tamanho.") |
| return "⚠️ O arquivo é muito grande. Máx. 15 MB." |
| if file_size == 0: |
| logger.warning(f"Arquivo {original_filename} está vazio.") |
| return "⚠️ Arquivo vazio." |
| except OSError as size_e: |
| logger.warning(f"Não foi possível verificar o tamanho do arquivo: {size_e}") |
|
|
| if filename_lower.endswith('.pdf'): |
| return extract_text_from_pdf(file_path) |
| elif filename_lower.endswith('.docx'): |
| return extract_text_from_docx(file_path) |
| elif filename_lower.endswith('.txt'): |
| return extract_text_from_txt(file_path) |
| else: |
| logger.warning(f"Formato de arquivo não suportado: {original_filename}") |
| return f"⚠️ Formato de arquivo não suportado ({os.path.splitext(original_filename)[1]}). Use PDF, DOCX ou TXT." |
|
|
| except FileNotFoundError: |
| logger.error(f"Erro: Arquivo temporário não encontrado '{getattr(file_obj, 'name', 'N/A')}'", exc_info=True) |
| return f"❌ Erro: Arquivo não encontrado. Tente reenviar." |
| except PermissionError: |
| logger.error(f"Erro de permissão ao acessar o arquivo '{getattr(file_obj, 'name', 'N/A')}'", exc_info=True) |
| return f"❌ Erro: Permissão negada para ler o arquivo." |
| except PyPDF2.errors.PdfReadError as pdf_err: |
| logger.error(f"Erro PyPDF2 ao ler '{getattr(file_obj, 'name', 'N/A')}': {pdf_err}", exc_info=True) |
| return f"❌ Erro ao ler PDF (PyPDF2): {pdf_err}" |
| except docx.opc.exceptions.PackageNotFoundError: |
| logger.error(f"Erro DOCX: Arquivo não parece ser um DOCX válido '{getattr(file_obj, 'name', 'N/A')}'", exc_info=True) |
| return f"❌ Erro: O arquivo .docx parece estar corrompido ou não é um DOCX válido." |
| except Exception as e: |
| logger.error(f"Erro geral ao processar o arquivo '{getattr(file_obj, 'orig_name', 'N/A')}': {str(e)}", exc_info=True) |
| return f"❌ Erro inesperado ao processar o arquivo. Verifique se o arquivo não está corrompido ou protegido. Detalhe: {str(e)}" |
|
|
| def chamar_api(prompt, use_oab_config=False): |
| config_to_use = CONFIG_OAB if use_oab_config else CONFIG |
| generation_config = genai.GenerationConfig( |
| temperature=config_to_use["temperatura"], |
| max_output_tokens=config_to_use["max_tokens"], |
| top_p=0.95 |
| ) |
| model = genai.GenerativeModel(MODEL_NAME) |
| logger.info(f"Chamando API Gemini com {len(prompt)} chars. Temp: {config_to_use['temperatura']}, MaxTokens: {config_to_use['max_tokens']}") |
|
|
| for tentativa in range(CONFIG["max_retry_attempts"]): |
| try: |
| with rate_limiter: |
| start_req = time.time() |
| response = model.generate_content( |
| contents=prompt, |
| generation_config=generation_config |
| ) |
| end_req = time.time() |
| logger.info(f"API request time: {end_req - start_req:.2f}s") |
|
|
| texto_gerado = response.text.strip() |
| logger.info(f"API retornou com sucesso. Tamanho: {len(texto_gerado)} chars.") |
| if response.candidates and response.candidates[0].finish_reason.name == "MAX_TOKENS": |
| logger.warning("Resposta pode estar incompleta devido a MAX_TOKENS.") |
| return texto_gerado |
|
|
| except google.api_core.exceptions.ResourceExhausted as e: |
| wait_time = CONFIG["retry_delay"] * (tentativa + 2) |
| logger.warning(f"Erro 429 (Rate Limit). Tentativa {tentativa + 1}/{CONFIG['max_retry_attempts']}. Aguardando {wait_time}s... Erro: {e}") |
| if tentativa < CONFIG["max_retry_attempts"] - 1: |
| time.sleep(wait_time) |
| continue |
| else: |
| return "❌ Erro 429: Muitas requisições. Tente novamente em um minuto." |
| |
| except (google.api_core.exceptions.InternalServerError, google.api_core.exceptions.ServiceUnavailable) as e: |
| wait_time = CONFIG["retry_delay"] * (tentativa + 1) |
| logger.warning(f"Erro de servidor ({e.__class__.__name__}). Tentativa {tentativa + 1}/{CONFIG['max_retry_attempts']}. Aguardando {wait_time}s...") |
| if tentativa < CONFIG["max_retry_attempts"] - 1: |
| time.sleep(wait_time) |
| continue |
| else: |
| return f"❌ Erro de servidor da API após {CONFIG['max_retry_attempts']} tentativas. Tente novamente mais tarde." |
|
|
| except (genai.types.BlockedPromptException, genai.types.StopCandidateException) as e: |
| logger.error(f"Geração de conteúdo bloqueada pela API. Erro: {e}") |
| return f"❌ Solicitação bloqueada pela API por razões de segurança. Tente reformular seu texto. Detalhe: {e}" |
|
|
| except Exception as e: |
| logger.exception(f"Erro inesperado durante chamada API (tentativa {tentativa + 1}): {str(e)}") |
| if tentativa < CONFIG["max_retry_attempts"] - 1: |
| time.sleep(CONFIG["retry_delay"] * (tentativa + 1)) |
| continue |
| else: |
| return f"❌ Erro inesperado durante a comunicação com a API: {str(e)}" |
|
|
| logger.error("Falha na chamada API após todas as tentativas. Nenhuma resposta ou erro específico retornado.") |
| return "❌ Falha na comunicação com a API após todas as tentativas." |
|
|
|
|
| def get_instrucoes_gerais_avaliacao(modo_treinamento=False): |
| base = """ |
| **Estrutura da Avaliação (Geral - IA):** |
| 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 (Detalhes Adicionais):** |
| * Para cada Ponto a Melhorar, detalhe AINDA MAIS: |
| a) Explicação aprofundada do erro e sua consequência prática (ex: risco de inépcia, preclusão, sucumbência). |
| b) Base legal/doutrinária específica (cite artigos, súmulas, enunciados). |
| c) **Exemplo Concreto de Redação Corrigida** para o trecho específico apontado. |
| d) Dica prática relevante (pode usar o banco de dicas se aplicável ou criar uma nova). |
| e) Possível estratégia alternativa ou ponto de atenção relacionado. |
| * Destaque inconsistências ou falta de conexão entre a peça e os fatos do caso, citando trechos se necessário. |
| * Sugira estratégias jurídicas adicionais ou alternativas viáveis com base no caso. |
| """ |
| return base |
|
|
| def estruturar_prompt_analise_geral(caso, peticao, tipo_peticao, modo_treinamento=False): |
| instrucoes_gerais = get_instrucoes_gerais_avaliacao(modo_treinamento) |
| prompt_especifico = f""" |
| ## Análise de Peça Processual: {tipo_peticao} (Avaliação Geral IA - Critérios OAB Simulados) |
| |
| **Caso/Contexto Fornecido:** |
| ``` |
| {caso} |
| ``` |
| |
| **Peça do Aluno ({tipo_peticao}):** |
| ``` |
| {peticao} |
| ``` |
| |
| {instrucoes_gerais} |
| |
| **Requisitos Específicos para {tipo_peticao} (Checklist Mínimo Considerado pela IA):** |
| """ |
| requisitos = [] |
| if tipo_peticao == "Petição Inicial": |
| requisitos = [ |
| "- **Endereçamento e Competência:** Verificação do juízo e foro corretos (Art. 319, I, CPC; leis especiais como CDC Art. 101, I).", |
| "- **Qualificação Completa das Partes:** Nome, prenome, estado civil, profissão, CPF/CNPJ, e-mail, domicílio (Art. 319, II, CPC).", |
| "- **Exposição Clara e Lógica dos Fatos:** Causa de pedir remota, narrativa coerente.", |
| "- **Fundamentação Jurídica Pertinente:** Causa de pedir próxima, artigos de lei, doutrina/jurisprudência aplicáveis (Art. 319, III, CPC).", |
| "- **Pedidos Certos e Determinados:** Claros, específicos, e se possível, líquidos (Art. 319, IV, CPC). Cumulação correta (Art. 327).", |
| "- **Valor da Causa:** Correto e justificado (Art. 291-292, 319, V, CPC).", |
| "- **Indicação das Provas:** Especificação dos meios de prova pretendidos (Art. 319, VI, CPC).", |
| "- **Opção pela Audiência:** Indicação sobre interesse na audiência de conciliação/mediação (Art. 319, VII, CPC).", |
| "- **Pedido de Citação do Réu:** Requerimento expresso e forma (se necessário).", |
| "- **Requerimento de Juntada de Documentos:** Procuração, custas/JG, documentos essenciais.", |
| "- **Fechamento Formal:** Padrão OAB ('Nestes termos...', Local, data, Advogado, OAB)." |
| ] |
| elif tipo_peticao == "Contestação": |
| requisitos = [ |
| "- **Endereçamento:** Ao juízo correto da causa.", |
| "- **Identificação:** Número do processo, qualificação completa do réu.", |
| "- **Tempestividade:** Alegação (se prazo relevante ou prova fornecida).", |
| "- **Síntese da Inicial:** Breve resumo (opcional, mas comum).", |
| "- **Preliminares (Art. 337 CPC):** Alegar TODAS as aplicáveis antes do mérito (inexistência/nulidade citação, incompetência, inépcia, perempção, litispendência, coisa julgada, conexão, incapacidade, etc.).", |
| "- **Mérito - Impugnação Específica:** Rebater PONTO A PONTO os fatos alegados pelo autor (Art. 341 CPC).", |
| "- **Mérito - Defesa Material:** Apresentar a versão dos fatos do réu, defesa direta (nega fato/consequência) e/ou indireta (fato impeditivo, modificativo, extintivo).", |
| "- **Fundamentação Jurídica (Mérito):** Leis, doutrina, jurisprudência que sustentam a defesa.", |
| "- **Reconvenção (se aplicável):** Pedido do réu contra o autor no mesmo processo (Art. 343 CPC).", |
| "- **Pedidos:** **Acolhimento das preliminares** (com extinção ou remessa); NO MÉRITO, **IMPROCEDÊNCIA TOTAL** dos pedidos do autor; Condenação do autor em custas e honorários advocatícios (Art. 85 CPC).", |
| "- **Indicação de Provas:** Especificar as provas que o réu pretende produzir.", |
| "- **Fechamento Formal:** Padrão OAB." |
| ] |
| elif tipo_peticao == "Reclamação Trabalhista": |
| requisitos = [ |
| "- **Endereçamento:** Vara do Trabalho competente (Art. 651, CLT).", |
| "- **Qualificação Completa das Partes:** Reclamante e Reclamada, incluindo CPF/CNPJ (Art. 840, §1º, CLT).", |
| "- **Exposição dos Fatos:** Breve exposição dos fatos que resultam o dissídio (contratação, função, salário, jornada, demissão).", |
| "- **Fundamentação Jurídica:** Artigos da CLT, Súmulas do TST e legislação correlata aplicável ao caso.", |
| "- **Pedidos:** Certos, determinados e **com indicação de seu valor** (líquidos) (Art. 840, §1º, CLT). Ex: verbas rescisórias, horas extras, adicionais.", |
| "- **Valor da Causa:** Soma dos valores de todos os pedidos.", |
| "- **Indicação das Provas:** Especificação dos meios de prova pretendidos.", |
| "- **Notificação da Reclamada:** Pedido de notificação postal da reclamada.", |
| "- **Requerimentos Finais:** Pedido de procedência, juros, correção monetária, honorários sucumbenciais (Art. 791-A, CLT), justiça gratuita.", |
| "- **Fechamento Formal:** Padrão OAB." |
| ] |
| elif tipo_peticao == "Contestação Trabalhista": |
| requisitos = [ |
| "- **Endereçamento:** Ao juízo da causa (Vara do Trabalho).", |
| "- **Identificação:** Número do processo, qualificação da Reclamada.", |
| "- **Tempestividade:** Alegação da apresentação dentro do prazo legal.", |
| "- **Preliminares/Prejudiciais:** Inépcia, prescrição bienal/quinquenal, etc.", |
| "- **Mérito - Impugnação Específica:** Rebater todos os fatos e pedidos da inicial (Súmula 338 TST sobre jornada, por exemplo).", |
| "- **Fundamentação Jurídica (Mérito):** Leis, Súmulas e OJs que amparam a defesa.", |
| "- **Compensação/Dedução:** Alegar eventuais valores já pagos sob os mesmos títulos.", |
| "- **Pedidos:** Acolhimento das preliminares/prejudiciais; No mérito, IMPROCEDÊNCIA TOTAL dos pedidos; Condenação em honorários sucumbenciais.", |
| "- **Indicação de Provas:** Especificar as provas que a reclamada pretende produzir.", |
| "- **Fechamento Formal:** Padrão OAB." |
| ] |
| elif tipo_peticao == "Recurso Ordinário": |
| requisitos = [ |
| "- **Peça de Interposição:** Endereçada ao juízo 'a quo' (Vara do Trabalho).", |
| "- **Peça de Razões:** Endereçada ao Tribunal Regional do Trabalho (TRT).", |
| "- **Pressupostos de Admissibilidade:** Cabimento (sentença), Tempestividade (8 dias), Preparo (custas e **depósito recursal**, se empresa).", |
| "- **Fundamentação da Reforma (Razões):** Princípio da dialeticidade - Impugnar especificamente os fundamentos da sentença recorrida.", |
| "- **Pedidos (Razões):** Conhecimento e Provimento do recurso para reformar a sentença; Inversão do ônus da sucumbência.", |
| "- **Fechamento Formal:** Padrão OAB (duas peças)." |
| ] |
| elif tipo_peticao == "Mandado de Segurança": |
| requisitos = [ |
| "- **Endereçamento e Competência:** Definição correta do juízo competente para julgar o ato da autoridade coatora.", |
| "- **Qualificação:** Impetrante, e indicação da Autoridade Coatora e da pessoa jurídica a que pertence.", |
| "- **Cabimento:** Demonstração da ausência de amparo por Habeas Corpus/Data e de recurso com efeito suspensivo.", |
| "- **Exposição dos Fatos e do Ato Coator:** Narrativa clara do ocorrido e do ato específico tido como ilegal ou abusivo.", |
| "- **Fundamentação - Direito Líquido e Certo:** Prova pré-constituída e demonstração inequívoca do direito violado (Lei 12.016/09).", |
| "- **Pedido Liminar:** Se houver urgência (fumus boni iuris e periculum in mora), pedir a suspensão do ato coator.", |
| "- **Pedidos Finais:** Notificação da autoridade, ciência ao órgão de representação judicial, concessão da segurança para anular/modificar o ato, condenação em custas.", |
| "- **Valor da Causa:** Geralmente para fins fiscais.", |
| "- **Fechamento Formal:** Padrão OAB." |
| ] |
| else: |
| requisitos.append("- Requisitos específicos para este tipo de peça não detalhados no prompt. A IA avaliará com base em conhecimento jurídico geral e nos critérios fundamentais.") |
|
|
| prompt_especifico += "\n".join(requisitos) |
| prompt_especifico += "\n\n**Instruções Finais:** Analise a peça do aluno CRITICAMENTE com base no caso, nos critérios gerais e nos requisitos específicos listados. Use seu conhecimento jurídico para simular um examinador da OAB rigoroso, mas justo. Forneça feedback construtivo, claro e detalhado. Gere a resposta **exclusivamente** no formato solicitado (Análise Geral, Nota, Pontos Positivos, Pontos a Melhorar detalhados, Análise Específica). Foque na aplicação correta do direito aos fatos do caso." |
| return prompt_especifico |
|
|
| def estruturar_prompt_geracao_modelo_oab(enunciado, gabarito, distribuicao): |
| prompt = f""" |
| **Tarefa:** Elabore a peça processual completa e **fundamentada** para o **Enunciado** fornecido. Utilize o **Gabarito Comentado** como guia OBRIGATÓRIO para os argumentos jurídicos essenciais e a **Distribuição dos Pontos** para garantir que TODOS os itens estruturais e pedidos necessários sejam incluídos e abordados corretamente. O objetivo é criar uma peça modelo que desenvolva os pontos esperados pela OAB, usando **ESTRITAMENTE** os fatos e dados fornecidos no Enunciado, Gabarito e Distribuição. |
| |
| **Enunciado:** |
| ``` |
| {enunciado} |
| ``` |
| |
| **Gabarito Comentado (Diretrizes de Conteúdo e Fundamentação a serem DESENVOLVIDAS):** |
| ``` |
| {gabarito} |
| ``` |
| |
| **Distribuição dos Pontos (Estrutura e Itens Essenciais a serem INCLUÍDOS):** |
| ``` |
| {distribuicao} |
| ``` |
| |
| **Instruções DETALHADAS E RESTRITIVAS para Geração:** |
| 1. **PROIBIDO INVENTAR DADOS:** NÃO invente NENHUMA informação que não esteja EXPLICITAMENTE presente no Enunciado, Gabarito ou Distribuição. Isso inclui, mas não se limita a: |
| * Endereços exatos (rua, número, bairro, CEP) - Use "XXX" se não fornecido. |
| * Números de documentos (RG, CPF) - Use "XXX" se não fornecido. |
| * Endereços de e-mail - Use "XXX" ou omita se não fornecido. |
| * Números de telefone - Use "XXX" ou omita se não fornecido. |
| * **Valores monetários específicos** para pedidos (alimentos, danos, etc.) ou valor da causa, A MENOS QUE o valor esteja CLARAMENTE especificado nos textos fornecidos. Se não houver valor, peça "a ser arbitrado por Vossa Excelência" ou use "valor simbólico" ou calcule conforme regras legais (ex: 12x alimentos a serem arbitrados para valor da causa). NÃO invente um número como R$ 3.000,00. |
| * Nomes de médicos, números de CRM, ou outros detalhes factuais não mencionados. |
| 2. **USE "XXX" OBRIGATORIAMENTE:** Para TODA E QUALQUER informação específica necessária pela estrutura da peça (ex: qualificação, endereço, documentos) que NÃO foi fornecida no Enunciado, use **"XXX"**. |
| 3. **Qualificação:** Inclua os dados MENCIONADOS no Enunciado (nome, nacionalidade, estado civil, profissão). Para os DEMAIS dados exigidos pelo Art. 319, II, CPC que não foram fornecidos, use "XXX". |
| 4. **Fatos:** Narre APENAS os fatos apresentados no **Enunciado**, de forma cronológica e objetiva. NÃO adicione detalhes ou circunstâncias não descritas. |
| 5. **Fundamentação Jurídica:** Desenvolva os argumentos legais mencionados no **Gabarito Comentado**, citando os artigos de lei mencionados. Conecte a lei aos fatos **DO ENUNCIADO**. |
| 6. **Tutela de Urgência/Antecipada:** Se o gabarito/distribuição mencionar, argumente sobre os requisitos (fumus/periculum) usando **FATOS DO ENUNCIADO** para demonstrá-los. NÃO invente fatos para justificar a urgência. |
| 7. **Pedidos:** Formule **TODOS** os pedidos listados na **Distribuição dos Pontos** e/ou **Gabarito**. Se um valor monetário for pedido, e ele NÃO foi dado no enunciado, peça "valor a ser arbitrado" ou use uma fórmula genérica baseada na lei (ex: percentual sobre rendimentos, se aplicável e mencionado). |
| 8. **Valor da Causa:** Atribua um valor à causa seguindo as regras do CPC (Art. 292). Se depender de valores não fornecidos no enunciado (como alimentos a serem arbitrados), indique a fórmula (ex: "12 vezes o valor mensal dos alimentos a serem arbitrados") ou um valor simbólico se a lei permitir/exigir. NÃO invente um valor numérico alto arbitrariamente. |
| 9. **Estrutura:** Siga a ordem lógica (Endereçamento, Qualificação, Fatos, Direito, Tutela, Pedidos, Valor da Causa, Fechamento). |
| 10. **Linguagem:** Formal e técnica (padrão OAB). |
| 11. **Finalização:** Use "Local, data.", "ADVOGADO(A)", "OAB/...". NÃO se identifique. |
| 12. **Exclusividade:** Baseie-se **SOMENTE** nas informações dos textos fornecidos. A violação desta regra (inventar dados) compromete a tarefa. |
| 13. **Output:** Gere **APENAS** o texto completo da peça processual. Sem comentários ou títulos fora da peça. |
| """ |
| return prompt |
|
|
|
|
| def estruturar_prompt_peticao_corrigida_geral(caso, peticao_original, tipo_peticao): |
| prompt = f""" |
| **Caso/Contexto Fornecido:** |
| ``` |
| {caso} |
| ``` |
| |
| **Peça Original do Aluno ({tipo_peticao}):** |
| ``` |
| {peticao_original} |
| ``` |
| |
| **Tarefa:** Com base no caso e nos problemas identificados na peça original (se houver), **gere uma versão corrigida e aprimorada** da peça ({tipo_peticao}). Aja como um advogado experiente preparando esta peça para protocolo, aplicando as melhores práticas e todos os requisitos formais e materiais exigidos para este tipo de peça no contexto do caso apresentado, simulando o padrão esperado em um exame da OAB. **Corrija erros, omissões e melhore a clareza, fundamentação e estrutura.** |
| |
| **Instruções Específicas para a Peça Corrigida ({tipo_peticao}):** |
| """ |
| instrucoes_tipo = [] |
| if tipo_peticao == "Petição Inicial": |
| instrucoes_tipo = [ |
| "1. **Endereçamento:** Defina o juízo e foro competentes com base nos fatos do caso (Vara Cível/Família/Fazenda, Comarca).", |
| "2. **Qualificação:** Completa (Art. 319, II), usando 'XXX' para dados ausentes no caso.", |
| "3. **Nome da Ação:** Adequado à pretensão.", |
| "4. **Fatos:** Reestruture a narrativa para ser cronológica, clara, objetiva e completa, usando apenas os fatos do caso.", |
| "5. **Fundamentação Jurídica:** Apresente os fundamentos legais (artigos de lei), doutrinários e jurisprudenciais pertinentes e **aplicados ao caso concreto**.", |
| "6. **Tutela Provisória (se cabível):** Se os fatos indicarem urgência ou evidência, inclua um tópico específico, fundamentando os requisitos (fumus/periculum ou Art. 311).", |
| "7. **Pedidos:** Liste de forma clara, certa e determinada. Inclua pedidos principais, acessórios (juros, correção), e processuais (citação, procedência, sucumbência).", |
| "8. **Requerimentos Finais:** Citação do réu, produção de provas (especificar minimamente), opção pela audiência (Art. 319, VII), pedido de JG ou juntada de custas.", |
| "9. **Valor da Causa:** Atribua e justifique conforme Art. 292.", |
| "10. **Fechamento:** Padrão OAB (Local, data, Advogado(a), OAB/...).", |
| ] |
| elif tipo_peticao == "Contestação": |
| instrucoes_tipo = [ |
| "1. **Endereçamento:** Ao juízo da causa.", |
| "2. **Identificação:** Processo, qualificação completa do réu.", |
| "3. **Tempestividade:** Mencionar.", |
| "4. **Estrutura:** Síntese da inicial (opcional); Preliminares (Art. 337, TODAS aplicáveis); Mérito (Impugnação Específica PONTO A PONTO + Defesa Material direta/indireta bem fundamentada); Reconvenção (se cabível, em peça separada ou na própria contestação).", |
| "5. **Pedidos:** Acolhimento PRELIMINARES (com consequências); NO MÉRITO, **IMPROCEDÊNCIA TOTAL** dos pedidos autorais; Procedência da RECONVENÇÃO (se houver); Condenação do autor em custas e honorários.", |
| "6. **Requerimentos:** Produção de provas pelo réu.", |
| "7. **Fechamento:** Padrão OAB.", |
| ] |
| elif tipo_peticao == "Reclamação Trabalhista": |
| instrucoes_tipo = [ |
| "1. **Endereçamento:** 'AO JUÍZO DA ___ VARA DO TRABALHO DE ___'.", |
| "2. **Qualificação:** Completa do Reclamante e da Reclamada (Art. 840, CLT).", |
| "3. **Fatos:** Narrativa clara e cronológica da relação de emprego.", |
| "4. **Direito:** Tópicos separados para cada verba/direito pleiteado (horas extras, verbas rescisórias, adicionais), com fundamentação legal (CLT, Súmulas).", |
| "5. **Pedidos:** Lista de pedidos certos, determinados e **líquidos (com valor)**, em conformidade com o Art. 840, §1º, da CLT.", |
| "6. **Requerimentos:** Notificação da reclamada, produção de provas, justiça gratuita, honorários de sucumbência.", |
| "7. **Valor da Causa:** A soma dos valores dos pedidos.", |
| "8. **Fechamento:** Padrão OAB.", |
| ] |
| elif tipo_peticao == "Mandado de Segurança": |
| instrucoes_tipo = [ |
| "1. **Endereçamento:** Juízo competente para julgar o ato da autoridade coatora.", |
| "2. **Qualificação:** Completa do Impetrante. Indicação precisa da Autoridade Coatora e do órgão/pessoa jurídica vinculada.", |
| "3. **Estrutura:** Tópicos claros para 'Do Cabimento', 'Dos Fatos e do Ato Coator', 'Do Direito Líquido e Certo', 'Da Medida Liminar' (se houver).", |
| "4. **Prova Pré-Constituída:** Enfatizar que as provas do direito estão anexas e são inequívocas.", |
| "5. **Pedidos:** Deferimento da liminar (se pedida); Notificação da autoridade; Ciência ao órgão de representação; No mérito, a **concessão da segurança** para o fim específico (anular/corrigir o ato); condenação em custas.", |
| "6. **Fechamento:** Padrão OAB.", |
| ] |
| else: |
| instrucoes_tipo.append("1. Siga a estrutura padrão para este tipo de peça.") |
| instrucoes_tipo.append("2. Fundamente juridicamente a pretensão ou defesa.") |
| instrucoes_tipo.append("3. Formule pedidos claros e coerentes.") |
| instrucoes_tipo.append("4. Use linguagem técnica adequada.") |
| instrucoes_tipo.append("5. Feche no padrão OAB.") |
|
|
| prompt += "\n".join(instrucoes_tipo) |
| 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, explicação, ou título como 'Peça Corrigida' ANTES ou DEPOIS do texto da peça." |
| return prompt |
|
|
|
|
| def pos_corrigir_peticao(peticao_corrigida, tipo_peticao): |
| if not peticao_corrigida or peticao_corrigida.strip() == "": |
| logger.warning(f"Pós-correção recebida petição vazia para tipo {tipo_peticao}.") |
| return "⚠️ (Pós-processamento falhou: Peça corrigida estava vazia)" |
|
|
| linhas = [l.strip() for l in peticao_corrigida.strip().split('\n')] |
| linhas_sem_vazias_extras = [] |
| last_line_was_blank = False |
| for linha in linhas: |
| if linha: |
| linhas_sem_vazias_extras.append(linha) |
| last_line_was_blank = False |
| elif not last_line_was_blank: |
| linhas_sem_vazias_extras.append("") |
| last_line_was_blank = True |
| while linhas_sem_vazias_extras and not linhas_sem_vazias_extras[-1]: |
| linhas_sem_vazias_extras.pop() |
|
|
| peticao_corrigida = "\n".join(linhas_sem_vazias_extras) |
|
|
| peticao_corrigida = re.sub(r"[Nn]estes termos,?\s*pede deferimento\.?", "Nestes termos, pede deferimento.", peticao_corrigida) |
|
|
| insertion_point = -1 |
| ntpd_match = re.search(r"Nestes termos, pede deferimento\.", peticao_corrigida, re.IGNORECASE) |
| if ntpd_match: |
| insertion_point = ntpd_match.start() |
| else: |
| vc_match = re.search(r"D[aá]\s*à\s*causa\s*o\s*valor\s*de", peticao_corrigida, re.IGNORECASE) |
| if vc_match: |
| insertion_point = vc_match.start() |
|
|
| prefix = peticao_corrigida |
| suffix = "" |
| if insertion_point != -1: |
| prefix = peticao_corrigida[:insertion_point].strip() |
| suffix = "\n" + peticao_corrigida[insertion_point:].strip() |
| else: |
| fechamento_pattern = r"(Local,\s*data\.|ADVOGADO\(A\)|OAB/\.\.\.)" |
| closing_match = list(re.finditer(fechamento_pattern, peticao_corrigida, re.IGNORECASE)) |
| if closing_match: |
| insertion_point = closing_match[0].start() |
| prefix = peticao_corrigida[:insertion_point].strip() |
| suffix = "\n" + peticao_corrigida[insertion_point:].strip() |
|
|
| if tipo_peticao in ["Petição Inicial", "Ação de Alimentos Gravídicos"]: |
| custas_jg_present = re.search(r"(juntada\s*d[ao]s?\s*comprovantes?\s*d[ae]\s*custas|recolhimento\s*d[ae]s?\s*custas|gratuidade\s*d[ae]?\s*justiça|benefícios?\s*d[ae]?\s*justiça\s*gratuita|JG)", prefix + suffix, re.IGNORECASE) |
| if not custas_jg_present: |
| logger.warning(f"Adicionando requerimento de custas/JG ausente para {tipo_peticao}") |
| prefix += "\nRequer, por fim, a juntada do comprovante de recolhimento das custas processuais." |
|
|
| if tipo_peticao in ["Petição Inicial", "Ação de Alimentos Gravídicos"]: |
| audiencia_present = re.search(r"(audiência\s*d[eao]?\s*(conciliação|mediação)|Art\.\s*319,\s*VII|Art\.\s*334)", prefix + suffix, re.IGNORECASE) |
| if not audiencia_present: |
| logger.warning(f"Adicionando opção de audiência ausente para {tipo_peticao}") |
| prefix += "\nNos termos do Art. 319, VII, do CPC, informa o Autor ter interesse na designação de audiência de conciliação ou mediação." |
|
|
| peticao_corrigida = prefix.strip() + suffix.strip() |
|
|
| linhas_finais = peticao_corrigida.split('\n') |
| fechamento_padrao = ["Nestes termos, pede deferimento.", "Local, data.", "ADVOGADO(A)", "OAB/..."] |
| termos_busca_fechamento = ["advogado", "oab", "local", "data", "pede deferimento", "termos,", "termos em que"] |
|
|
| cleaned_lines = [] |
| end_content_index = len(linhas_finais) |
| for i in range(len(linhas_finais) - 1, -1, -1): |
| linha_lower = linhas_finais[i].lower().strip() |
| is_closing_line = any(term in linha_lower for term in termos_busca_fechamento) and len(linha_lower) < 50 |
| if not is_closing_line and linha_lower != "": |
| end_content_index = i + 1 |
| break |
| elif linha_lower == "": |
| continue |
| else: |
| pass |
|
|
| linhas_conteudo = linhas_finais[:end_content_index] |
|
|
| peticao_final = "\n".join(linhas_conteudo).strip() + "\n\n" + "\n".join(fechamento_padrao) |
|
|
| return peticao_final.strip() |
|
|
|
|
| def formatar_correcao(texto): |
| texto = re.sub(r"^\#\#\#\s*Análise Geral", "## 📝 Análise Geral", texto, flags=re.MULTILINE) |
| texto = re.sub(r"^\#\#\#\s*Nota Final", "## ⭐ Nota Final", texto, flags=re.MULTILINE) |
| texto = re.sub(r"^\#\#\#\s*Pontos Positivos", "## ✅ Pontos Positivos", texto, flags=re.MULTILINE) |
| texto = re.sub(r"^\#\#\#\s*Pontos a Melhorar", "## ⚠️ Pontos a Melhorar", texto, flags=re.MULTILINE) |
| texto = re.sub(r"^\#\#\#\s*Análise Específica dos Requisitos", "## 🔎 Análise Específica dos Requisitos", texto, flags=re.MULTILINE) |
|
|
| linhas = texto.split('\n') |
| linhas_formatadas = [] |
| in_list = False |
| for linha in linhas: |
| linha_strip = linha.strip() |
|
|
| match_num_list = re.match(r"^\s*(\d+)\.\s*(.*)", linha) |
| match_bullet_list = re.match(r"^\s*([\*\-])\s+(.*)", linha) |
| match_sub_list = re.match(r"^\s+([a-z])\)\s+(.*)", linha) |
| match_sub_sub_list = re.match(r"^\s{4,}([ivx]+)\.\s+(.*)", linha) |
|
|
| if match_num_list: |
| num, content = match_num_list.groups() |
| linhas_formatadas.append(f"\n**{num}. {content.strip()}**") |
| in_list = False |
| elif match_bullet_list: |
| bullet, content = match_bullet_list.groups() |
| prefix = "\n* " if not linhas_formatadas or not linhas_formatadas[-1].strip().endswith(("*",":")) else "* " |
| linhas_formatadas.append(prefix + content.strip()) |
| in_list = True |
| elif match_sub_list: |
| letter, content = match_sub_list.groups() |
| linhas_formatadas.append(f" * {letter}) {content.strip()}") |
| in_list = True |
| elif match_sub_sub_list: |
| roman, content = match_sub_sub_list.groups() |
| linhas_formatadas.append(f" - {roman}. {content.strip()}") |
| in_list = True |
| else: |
| if linha.startswith(" ") or linha.startswith("```"): |
| linhas_formatadas.append(linha) |
| else: |
| if linha_strip and (not linhas_formatadas or linhas_formatadas[-1].strip() == "" or linhas_formatadas[-1].startswith("##")): |
| linhas_formatadas.append(linha_strip) |
| elif linha_strip: |
| linhas_formatadas.append(" " + linha_strip) |
| elif linhas_formatadas and linhas_formatadas[-1].strip() != "": |
| linhas_formatadas.append("") |
|
|
| if not match_bullet_list and not match_sub_list and not match_sub_sub_list: |
| in_list = False |
|
|
|
|
| texto_final = "\n".join(linhas_formatadas) |
|
|
| nota_pattern = r"(\*\*?\s*(?:Nota|Nota Final)\s*:\s*\*\*?\s*)(\d{1,2}(?:[,.]\d)?)(?:\s*/\s*10)?(\s*\*\*?)" |
| match = re.search(nota_pattern, texto_final, re.IGNORECASE | re.DOTALL) |
| if match: |
| prefix, nota_str, suffix = match.groups() |
| try: |
| nota_float = float(nota_str.replace(",", ".")) |
| nota_formatada = f"{nota_float:.1f}/10" |
| texto_final = re.sub(nota_pattern, f"{prefix.strip()} **{nota_formatada}**{suffix.strip()}", texto_final, count=1, flags=re.IGNORECASE | re.DOTALL) |
| except ValueError: |
| logger.warning(f"Não foi possível formatar a nota encontrada: {nota_str}") |
|
|
| texto_final = re.sub(r"([^\n])\n(##\s)", r"\1\n\n\2", texto_final) |
|
|
| return texto_final.strip() |
|
|
|
|
| def corrigir_peticao_geral_ia(caso, tipo_peticao, peticao_texto=None, peticao_arquivo=None, modo_treinamento=False): |
| logger.info(f"Iniciando correção GERAL (IA) para tipo: {tipo_peticao}, Modo Treinamento: {modo_treinamento}") |
| start_time = time.time() |
| processing_log = [] |
|
|
| if not caso or not caso.strip(): |
| return "⚠️ **Erro:** Forneça o texto do caso/enunciado.", "", "", "", "", "", "" |
| if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO: |
| return f"⚠️ **Erro:** Selecione um Tipo de Peça válido. Tipos disponíveis: {', '.join(TIPOS_DE_PETICAO)}", "", "", "", "", "", "" |
|
|
| peticao_conteudo = "" |
| input_source = "" |
| if peticao_arquivo is not None: |
| input_source = f"arquivo '{getattr(peticao_arquivo, 'orig_name', 'N/A')}'" |
| logger.info(f"Processando petição do {input_source}") |
| peticao_conteudo = process_file(peticao_arquivo) |
| if peticao_conteudo.startswith("❌") or peticao_conteudo.startswith("⚠️"): |
| logger.error(f"Falha ao processar arquivo: {peticao_conteudo}") |
| return f"**Erro ao Ler Arquivo:**\n{peticao_conteudo}", "", "", "", "", "", "" |
| processing_log.append(f"✅ Texto extraído do {input_source}.") |
| elif peticao_texto and peticao_texto.strip(): |
| input_source = "campo de texto" |
| logger.info(f"Processando petição do {input_source}") |
| peticao_conteudo = peticao_texto.strip() |
| processing_log.append(f"✅ Texto da petição obtido do {input_source}.") |
| else: |
| return "⚠️ **Erro:** Forneça a petição (seja via texto ou arquivo PDF/DOCX/TXT).", "", "", "", "", "", "" |
|
|
| if len(peticao_conteudo) < 50: |
| logger.warning(f"Petição parece muito curta ({len(peticao_conteudo)} chars). Verificando triggers...") |
| troll_triggers = ["faça a petição", "escreva a peça", "elabore a petição", "crie a petição", "gerar a petição", "preciso de uma petição"] |
| peticao_lower = peticao_conteudo.lower() |
| if any(trigger in peticao_lower for trigger in troll_triggers): |
| logger.warning("Trigger de 'faça por mim' detectado.") |
| return random.choice([ |
| "✋ Opa! A ideia é **você** fazer a peça e **eu** corrigir. 😉 Tente elaborar a petição e me envie para análise!", |
| "📝 Hmm, parece que você está pedindo para eu fazer a peça. Que tal você tentar primeiro? Envie sua versão que eu ajudo a melhorar!", |
| "🤖 Meu trabalho é ser seu corretor, não seu redator! Escreva sua petição com base no caso e eu darei o feedback. 👍", |
| "🧐 Entendido! Mas para corrigir, preciso da *sua* versão da peça. Elabore-a e submeta para avaliação." |
| ]), peticao_conteudo, "", "", "", "", "" |
| else: |
| processing_log.append("⚠️ Atenção: A petição enviada é muito curta, a análise pode ser limitada.") |
|
|
|
|
| cache_key = f"geral|{tipo_peticao}|{hash(caso)}|{hash(peticao_conteudo)}|{modo_treinamento}" |
| if cache_key in CORRECAO_CACHE: |
| logger.info("Correção GERAL (IA) encontrada no cache.") |
| cached_data = CORRECAO_CACHE[cache_key] |
| required_keys = ["analise_formatada", "peticao_original", "peticao_corrigida", |
| "resumo_diff", "criticas_diff", "avaliacao_criterios_ia", "diff_html"] |
| if all(key in cached_data for key in required_keys): |
| return ( cached_data["analise_formatada"], cached_data["peticao_original"], |
| cached_data["peticao_corrigida"], cached_data["resumo_diff"], |
| cached_data["criticas_diff"], cached_data.get("avaliacao_criterios_ia", ""), |
| cached_data["diff_html"] ) |
| else: |
| logger.warning("Cache hit, but data incomplete. Regenerating.") |
|
|
|
|
| logger.info("Estruturando e enviando prompt de ANÁLISE para a API...") |
| prompt_analise = estruturar_prompt_analise_geral(caso, peticao_conteudo, tipo_peticao, modo_treinamento) |
| processing_log.append("⏳ Enviando pedido de análise para a IA...") |
| analise_bruta = chamar_api(prompt_analise, use_oab_config=False) |
|
|
| if analise_bruta.startswith("❌") or analise_bruta.startswith("⏱️") or analise_bruta.startswith("⚠️"): |
| logger.error(f"Falha na API (Análise): {analise_bruta}") |
| return f"**Erro na Análise da IA:**\n{analise_bruta}", peticao_conteudo, "", "", "", "", "" |
| processing_log.append("✅ Análise da IA recebida.") |
| logger.info("Análise da IA recebida, formatando...") |
| analise_formatada = formatar_correcao(analise_bruta) |
| processing_log.append("✅ Análise formatada.") |
|
|
|
|
| logger.info("Estruturando e enviando prompt de CORREÇÃO para a API...") |
| prompt_peticao = estruturar_prompt_peticao_corrigida_geral(caso, peticao_conteudo, tipo_peticao) |
| processing_log.append("⏳ Enviando pedido de peça corrigida para a IA...") |
| peticao_corrigida_bruta = chamar_api(prompt_peticao, use_oab_config=False) |
|
|
| peticao_corrigida_final = "" |
| if peticao_corrigida_bruta.startswith("❌") or peticao_corrigida_bruta.startswith("⏱️") or peticao_corrigida_bruta.startswith("⚠️"): |
| logger.error(f"Falha na API (Correção): {peticao_corrigida_bruta}") |
| peticao_corrigida_final = f"⚠️ **Falha ao gerar a peça corrigida pela IA:**\n{peticao_corrigida_bruta}" |
| processing_log.append(f"❌ Falha ao gerar peça corrigida: {peticao_corrigida_bruta}") |
| else: |
| processing_log.append("✅ Peça corrigida (bruta) recebida da IA.") |
| logger.info("Peça corrigida recebida, pós-processando...") |
| peticao_corrigida_final = pos_corrigir_peticao(peticao_corrigida_bruta, tipo_peticao) |
| processing_log.append("✅ Peça corrigida pós-processada.") |
|
|
|
|
| logger.info("Calculando Diffs entre original e corrigida...") |
| processing_log.append("⏳ Calculando diferenças...") |
| diff_html, resumo_diff, criticas_diff = "", "", "" |
| if not peticao_corrigida_final.startswith("⚠️"): |
| try: |
| diff_html, resumo_diff, criticas_diff = calcular_diff_interno(peticao_conteudo, peticao_corrigida_final) |
| processing_log.append("✅ Diferenças calculadas.") |
| logger.info("Diffs calculados.") |
| except Exception as diff_e: |
| logger.error(f"Erro ao calcular diff: {diff_e}", exc_info=True) |
| processing_log.append(f"❌ Erro ao calcular diferenças: {diff_e}") |
| resumo_diff = "Erro ao gerar resumo diff." |
| criticas_diff = "Erro ao gerar críticas diff." |
| diff_html = f"<p style='color:red;'>Erro ao gerar visualização diff: {escape(str(diff_e))}</p>" |
| else: |
| processing_log.append("⚠️ Diferenças não calculadas (falha na geração da peça corrigida).") |
| resumo_diff = "N/A (Peça corrigida não gerada)" |
| criticas_diff = "N/A (Peça corrigida não gerada)" |
| diff_html = "<p>Visualização não disponível (peça corrigida não foi gerada).</p>" |
|
|
|
|
| avaliacao_criterios_md = "*(Avaliação específica de critérios ainda não implementada nesta extração)*" |
| crit_match = re.search(r"##\s*🔎\s*Análise Específica dos Requisitos\s*(.*)", analise_formatada, re.DOTALL | re.IGNORECASE) |
| if crit_match: |
| avaliacao_criterios_md = crit_match.group(1).strip() |
|
|
| log_output = "### 📋 Log do Processamento:\n" + "\n".join([f"* {msg}" for msg in processing_log]) + "\n\n---\n" |
| correcao_completa_formatada = log_output + analise_formatada |
|
|
| resultado_historico = { |
| "data": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), |
| "modo": "Geral IA", |
| "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, |
| "avaliacao_criterios_ia": avaliacao_criterios_md, |
| } |
| 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, |
| "avaliacao_criterios_ia": avaliacao_criterios_md, |
| "diff_html": diff_html |
| } |
|
|
| end_time = time.time() |
| logger.info(f"Correção GERAL (IA) concluída em {end_time - start_time:.2f}s.") |
|
|
| return ( |
| correcao_completa_formatada, |
| peticao_conteudo, |
| peticao_corrigida_final, |
| resumo_diff, |
| criticas_diff, |
| avaliacao_criterios_md, |
| diff_html |
| ) |
|
|
|
|
| def parse_oab_pdf(file_obj): |
| if file_obj is None: return None, None, None, "⚠️ Nenhum PDF OAB fornecido." |
|
|
| file_path = getattr(file_obj, 'name', None) |
| original_filename = getattr(file_obj, 'orig_name', file_path) |
| logger.info(f"Iniciando parse do PDF OAB: {original_filename}") |
|
|
| if not file_path: return None, None, None, "❌ Erro interno: Caminho do arquivo PDF não encontrado." |
|
|
| try: |
| full_text = extract_text_from_pdf(file_path) |
|
|
| if not full_text or full_text.startswith("❌") or full_text.startswith("⚠️"): |
| logger.error(f"Falha na extração de texto do PDF OAB: {full_text}") |
| return None, None, None, f"❌ Falha ao extrair texto do PDF OAB: {full_text}" |
|
|
| logger.info("Texto extraído do PDF OAB, procurando seções...") |
| enunciado, gabarito, distribuicao = "", "", "" |
| current_section = None |
| lines = full_text.split('\n') |
|
|
| enunciado_header_pattern = re.compile(r"^\s*\W*\s*ENUNCIADO\s*\W*\s*$", re.IGNORECASE) |
| gabarito_header_pattern = re.compile(r"^\s*\W*\s*(GABARITO\s*(?:COMENTADO)?|PADRÃO\s*DE\s*RESPOSTA)\s*(?:[-–—]\s*(?:PEÇA|QUESTÃO))?\s*\W*\s*$", re.IGNORECASE) |
| distribuicao_header_pattern = re.compile(r"^\s*\W*\s*DISTRIBUIÇÃO\s*D[OA]S?\s*PONTOS\s*\W*\s*$", re.IGNORECASE) |
| end_section_pattern = re.compile(r"^\s*\W*\s*(?:PADRÃO\s*DE\s*RESPOSTA\s*[-–—]\s*QUEST(?:ÃO|ÕES)|QUEST(?:ÃO|ÕES)\s*DISCURSIVAS?)\s*\W*\s*$", re.IGNORECASE) |
|
|
|
|
| for line in lines: |
| line_strip = line.strip() |
|
|
| if end_section_pattern.match(line_strip): |
| logger.info(f"Fim da seção de peça detectado em: '{line_strip}'") |
| current_section = None |
| break |
|
|
| if enunciado_header_pattern.match(line_strip): |
| logger.info("Seção 'Enunciado' encontrada.") |
| current_section = "enunciado" |
| continue |
| elif gabarito_header_pattern.match(line_strip): |
| logger.info("Seção 'Gabarito/Padrão' encontrada.") |
| current_section = "gabarito" |
| if enunciado: gabarito += "\n---\n" |
| continue |
| elif distribuicao_header_pattern.match(line_strip): |
| logger.info("Seção 'Distribuição' encontrada.") |
| current_section = "distribuicao" |
| if gabarito: distribuicao += "\n---\n" |
| continue |
|
|
| if current_section == "enunciado": |
| if line_strip or (enunciado and not enunciado.endswith("\n\n")): |
| enunciado += line + "\n" |
| elif current_section == "gabarito": |
| if line_strip or (gabarito and not gabarito.endswith("\n\n")): |
| gabarito += line + "\n" |
| elif current_section == "distribuicao": |
| if line_strip or (distribuicao and not distribuicao.endswith("\n\n")): |
| if not re.match(r"^\s*(?:Página\s*\d+|\d+\s*$|EXAME DE ORDEM|FGV)", line_strip, re.IGNORECASE): |
| distribuicao += line + "\n" |
|
|
|
|
| enunciado = enunciado.strip() |
| gabarito = gabarito.strip() |
| distribuicao = distribuicao.strip() |
|
|
| missing_sections = [] |
| if not enunciado: missing_sections.append("Enunciado") |
| if not gabarito: missing_sections.append("Gabarito/Padrão de Resposta") |
| if not distribuicao: missing_sections.append("Distribuição de Pontos") |
|
|
| if missing_sections: |
| logger.warning(f"Parse OAB PDF falhou em encontrar seções: {', '.join(missing_sections)}") |
| preview_text = full_text[:1000].strip() |
| error_msg = (f"⚠️ Falha ao separar seções obrigatórias ({', '.join(missing_sections)}). " |
| f"Verifique se o PDF contém os cabeçalhos esperados (ex: 'Enunciado', 'Gabarito Comentado' ou 'Padrão de Resposta', 'Distribuição dos Pontos').\n\n" |
| f"**Início do Texto Extraído:**\n```\n{preview_text}...\n```") |
| return None, full_text, None, error_msg |
|
|
|
|
| logger.info("PDF OAB parseado com sucesso. Seções extraídas.") |
| return enunciado, gabarito, distribuicao, f"✅ PDF OAB carregado e seções extraídas (Enunciado: {len(enunciado)}c, Gabarito: {len(gabarito)}c, Distribuição: {len(distribuicao)}c)." |
|
|
| except Exception as e: |
| logger.error(f"Erro inesperado ao parsear PDF OAB ({original_filename}): {str(e)}", exc_info=True) |
| return None, None, None, f"❌ Erro inesperado ao processar PDF OAB: {str(e)}" |
|
|
|
|
| def gerar_peticao_modelo_oab(enunciado, gabarito, distribuicao): |
| if not enunciado or not gabarito or not distribuicao: |
| return "⚠️ **Erro:** Enunciado, Gabarito e Distribuição são necessários para gerar a peça modelo OAB." |
| if len(enunciado) < 50 or len(gabarito) < 50 or len(distribuicao) < 20: |
| return "⚠️ **Atenção:** O conteúdo do Enunciado, Gabarito ou Distribuição parece muito curto. Verifique se as seções foram extraídas corretamente do PDF ou inseridas manualmente." |
|
|
| logger.info("Iniciando geração da peça modelo OAB via API...") |
| prompt = estruturar_prompt_geracao_modelo_oab(enunciado, gabarito, distribuicao) |
| peticao_modelo_bruta = chamar_api(prompt, use_oab_config=True) |
|
|
| if peticao_modelo_bruta.startswith("❌") or peticao_modelo_bruta.startswith("⏱️") or peticao_modelo_bruta.startswith("⚠️"): |
| logger.error(f"Erro API ao gerar modelo OAB: {peticao_modelo_bruta}") |
| return f"❌ **Falha ao gerar peça modelo OAB pela IA:**\n{peticao_modelo_bruta}" |
|
|
| logger.info("Peça modelo OAB (bruta) recebida da API. Pós-processando...") |
| peticao_modelo_final = pos_corrigir_peticao(peticao_modelo_bruta, "Petição Desconhecida (Modelo OAB)") |
| logger.info("Pós-processamento da peça modelo OAB concluído.") |
| return peticao_modelo_final |
|
|
|
|
| def estruturar_prompt_correcao_pela_oab(peticao_usuario, enunciado, gabarito, distribuicao): |
| total_pontos_max = 5.0 |
| try: |
| pontos_encontrados = re.findall(r"\(?\s*(\d+[,.]\d+)\s*\)?", distribuicao) |
| if pontos_encontrados: |
| total_pontos_max = sum(float(p.replace(",", ".")) for p in pontos_encontrados) |
| logger.info(f"Pontuação máxima calculada da distribuição: {total_pontos_max}") |
| else: |
| logger.warning("Não foi possível calcular pontuação máxima da distribuição via regex. Usando padrão 5.0.") |
| except Exception as e: |
| logger.error(f"Erro ao calcular pontuação máxima da distribuição: {e}. Usando padrão 5.0.") |
| total_pontos_max = 5.0 |
|
|
| prompt = f""" |
| **Tarefa:** Avalie a **Peça do Usuário** comparando-a **rigorosamente** com o padrão oficial da OAB fornecido (Enunciado, Gabarito Comentado, Distribuição dos Pontos). Atribua pontuação para cada item da distribuição e forneça feedback detalhado. |
| |
| **Enunciado (Contexto do Caso):** |
| ``` |
| {enunciado} |
| ``` |
| |
| **Gabarito Comentado (Conteúdo e Fundamentação Esperados):** |
| ``` |
| {gabarito} |
| ``` |
| |
| **Distribuição dos Pontos (Critérios de Avaliação e Estrutura Obrigatória):** |
| ``` |
| {distribuicao} |
| ``` |
| |
| **Peça do Usuário (A ser Avaliada):** |
| ``` |
| {peticao_usuario} |
| ``` |
| |
| **Instruções Detalhadas para Avaliação:** |
| 1. **Análise Item a Item:** Percorra **CADA ITEM** listado na **Distribuição dos Pontos**. |
| 2. **Verificação de Conteúdo:** Para cada item, verifique se a **Peça do Usuário** abordou o ponto de forma correta e completa, conforme o **Gabarito Comentado**. |
| 3. **Atribuição de Pontuação:** Atribua a pontuação PARCIAL ou TOTAL prevista para o item na Distribuição, ou ZERO se ausente/incorreto. Seja rigoroso como um examinador OAB. |
| 4. **Justificativa Clara:** Para **CADA ITEM**, justifique brevemente a pontuação atribuída, explicando o acerto, erro ou omissão do usuário em relação ao padrão esperado (Gabarito/Distribuição). Cite trechos da peça do usuário se relevante. |
| 5. **Cálculo da Nota Final:** Some todas as pontuações obtidas nos itens. |
| 6. **Feedback Geral:** Inclua um breve resumo dos principais acertos e erros gerais da peça. |
| 7. **Formato de Saída (Obrigatório - Use Markdown):** |
| |
| ```markdown |
| ### 📊 Avaliação Detalhada Conforme Padrão OAB |
| |
| **Observação:** A avaliação abaixo segue estritamente os critérios da Distribuição de Pontos e o conteúdo do Gabarito Comentado fornecidos. |
| |
| **Pontuação por Item:** |
| |
| * **[Item 1 da Distribuição - Copiar Texto Exato] (.../... pts):** [Pontos Obtidos] / [Máximo Item 1] |
| * *Justificativa:* [Explicação concisa do acerto/erro/omissão do usuário em relação a este item específico, comparando com o Gabarito. Ex: "Usuário abordou corretamente...", "Usuário omitiu...", "Usuário fundamentou de forma diferente do gabarito..."]. |
| |
| * **[Item 2 da Distribuição - Copiar Texto Exato] (.../... pts):** [Pontos Obtidos] / [Máximo Item 2] |
| * *Justificativa:* [...]. |
| |
| * ... (Repetir para **TODOS** os itens da Distribuição) |
| |
| --- |
| |
| **Pontuação Total Estimada:** [Soma dos Pontos Obtidos] / {total_pontos_max:.2f} |
| |
| --- |
| |
| **Comentários Gerais e Pontos de Melhoria:** |
| * **Principais Acertos:** [Listar 2-3 pontos positivos gerais, se houver]. |
| * **Principais Omissões/Erros:** [Listar os erros ou omissões mais impactantes que não se encaixam perfeitamente nos itens pontuados, ou reforçar os mais graves]. |
| * **Sugestões:** [Breves sugestões para alinhamento ao padrão OAB]. |
| |
| --- |
| *(Fim da Avaliação)* |
| ``` |
| |
| **Importante:** |
| * Baseie **TODA** a avaliação **EXCLUSIVAMENTE** nos materiais OAB fornecidos (Enunciado, Gabarito, Distribuição). Não use conhecimento externo. |
| * Seja **objetivo** e **preciso** nas justificativas e pontuações. |
| * Não invente fatos ou critérios não presentes nos materiais. |
| * Use exatamente o formato Markdown especificado acima. |
| """ |
| return prompt |
|
|
|
|
| def corrigir_pela_oab(peticao_usuario, enunciado, gabarito, distribuicao): |
| if not peticao_usuario or not enunciado or not gabarito or not distribuicao: |
| return "⚠️ **Erro:** Petição do Usuário, Enunciado, Gabarito e Distribuição são necessários para a correção padrão OAB." |
| if len(peticao_usuario) < 50: |
| return "⚠️ **Atenção:** A peça do usuário parece muito curta para ser avaliada de forma significativa." |
| if len(enunciado) < 50 or len(gabarito) < 50 or len(distribuicao) < 20: |
| return "⚠️ **Atenção:** O conteúdo do Enunciado, Gabarito ou Distribuição parece muito curto. Verifique se as seções foram extraídas corretamente do PDF ou inseridas manualmente antes de prosseguir com a correção." |
|
|
| logger.info("Iniciando correção/avaliação conforme padrão OAB via API...") |
| prompt = estruturar_prompt_correcao_pela_oab(peticao_usuario, enunciado, gabarito, distribuicao) |
| correcao_oab_bruta = chamar_api(prompt, use_oab_config=True) |
|
|
| if correcao_oab_bruta.startswith("❌") or correcao_oab_bruta.startswith("⏱️") or correcao_oab_bruta.startswith("⚠️"): |
| logger.error(f"Erro API ao corrigir/avaliar OAB: {correcao_oab_bruta}") |
| return f"❌ **Falha ao corrigir/avaliar peça conforme padrão OAB pela IA:**\n{correcao_oab_bruta}" |
|
|
| logger.info("Correção/Avaliação OAB recebida da API.") |
| final_output = correcao_oab_bruta.replace("### 📊 Avaliação Detalhada Conforme Padrão OAB", "\n### 📊 Avaliação Detalhada Conforme Padrão OAB\n") |
| final_output = final_output.replace("**Pontuação por Item:**", "\n**Pontuação por Item:**\n") |
| final_output = final_output.replace("---", "\n---\n") |
| final_output = final_output.replace("**Pontuação Total Estimada:**", "\n**Pontuação Total Estimada:**\n") |
| final_output = final_output.replace("**Comentários Gerais e Pontos de Melhoria:**", "\n**Comentários Gerais e Pontos de Melhoria:**\n") |
|
|
| return final_output.strip() |
|
|
|
|
| def export_to_txt(content, filename_prefix): |
| if not content: |
| logger.warning("Tentativa de exportar conteúdo vazio para TXT.") |
| return None |
|
|
| try: |
| timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') |
| filename = f"{filename_prefix}_{timestamp}.txt" |
| filepath = os.path.join(tempfile.gettempdir(), filename) |
|
|
| if not isinstance(content, str): |
| content = str(content) |
|
|
| with open(filepath, 'w', encoding='utf-8') as f: |
| f.write(content) |
|
|
| logger.info(f"Conteúdo exportado para TXT: {filepath}") |
| return filepath |
| except Exception as e: |
| logger.error(f"Erro ao exportar para TXT ({filename_prefix}): {str(e)}", exc_info=True) |
| return None |
|
|
|
|
| class PDF(FPDF): |
| def header(self): |
| pass |
|
|
| def footer(self): |
| self.set_y(-15) |
| self.set_font('Arial', 'I', 8) |
| self.cell(0, 10, 'Página ' + str(self.page_no()) + '/{nb}', 0, 0, 'C') |
|
|
| def export_to_pdf(content, filename_prefix): |
| if not content: |
| logger.warning("Tentativa de exportar conteúdo vazio para PDF.") |
| return None |
|
|
| try: |
| timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') |
| filename = f"{filename_prefix}_{timestamp}.pdf" |
| filepath = os.path.join(tempfile.gettempdir(), filename) |
|
|
| if not isinstance(content, str): |
| content = str(content) |
|
|
| pdf = PDF() |
| pdf.alias_nb_pages() |
| pdf.add_page() |
| pdf.set_font('Arial', '', 10) |
|
|
| try: |
| content_for_pdf = content.encode('latin-1', 'replace').decode('latin-1') |
| except Exception as enc_e: |
| logger.warning(f"Erro ao codificar para Latin-1 no PDF, tentando UTF-8 com replace: {enc_e}") |
| content_for_pdf = content.encode('utf-8', 'replace').decode('utf-8') |
|
|
|
|
| pdf.multi_cell(0, 5, content_for_pdf) |
|
|
| pdf.output(filepath, 'F') |
|
|
| logger.info(f"Conteúdo exportado para PDF: {filepath}") |
| return filepath |
| except Exception as e: |
| logger.error(f"Erro ao exportar para PDF ({filename_prefix}): {str(e)}", exc_info=True) |
| if "cannot be encoded in latin-1" in str(e): |
| logger.error("PDF Export Failed: Content has characters not supported by the standard PDF font encoding (Latin-1). Consider installing/using a UTF-8 font like DejaVu.") |
| return None |
|
|
| def calcular_diff_interno(texto_original, texto_corrigido): |
| if not texto_original or not texto_corrigido or texto_corrigido.startswith("⚠️") or texto_corrigido.startswith("❌"): |
| logger.warning("Diff não calculado: textos inválidos ou ausentes.") |
| return "","N/A (Textos inválidos/ausentes)","N/A" |
|
|
| orig_lines = texto_original.replace('\r\n', '\n').splitlines() |
| corr_lines = texto_corrigido.replace('\r\n', '\n').splitlines() |
|
|
| if not orig_lines and not corr_lines: |
| return "<p>Ambos os textos estão vazios.</p>", "Vazio", "Nenhuma mudança." |
| if not orig_lines: |
| return f"<p>Texto original vazio. Texto corrigido adicionado:</p><div class='diff-container'><div class='linha-adicionada'><ins>{escape(texto_corrigido)}</ins></div></div>", f"Adicionadas: {len(corr_lines)}", "Texto corrigido adicionado." |
| if not corr_lines: |
| return f"<p>Texto corrigido vazio. Texto original removido:</p><div class='diff-container'><div class='linha-removida'><del>{escape(texto_original)}</del></div></div>", f"Removidas: {len(orig_lines)}", "Texto corrigido está vazio." |
|
|
|
|
| diff = difflib.ndiff(orig_lines, corr_lines) |
|
|
| resultado_diff_html = [] |
| criticas = [] |
| l_rem = 0 |
| l_add = 0 |
| l_igual = 0 |
|
|
| kw_rem_critico = ["invenção", "identificação", "erro crasso", "não fundamentado", "pedido inepto"] |
| kw_add_importante = ["custas", "art. 319", "art. 337", "lei 11.804", "tutela provisória", "preliminar", "impugnação específica", "honorários"] |
|
|
| for linha in diff: |
| prefix = linha[0] |
| texto = linha[2:] |
| texto_escaped = escape(texto) |
|
|
| if prefix == ' ': |
| resultado_diff_html.append(f"<div class='linha-igual'>{texto_escaped}</div>") |
| l_igual += 1 |
| elif prefix == '-': |
| l_rem += 1 |
| resultado_diff_html.append(f"<div class='linha-removida'><del>{texto_escaped}</del></div>") |
| if any(kw in texto.lower() for kw in kw_rem_critico): |
| criticas.append(f"<span style='color:red;'>🔴 REMOVIDO (Crítico):</span> {texto_escaped[:100]}{'...' if len(texto_escaped)>100 else ''}") |
| elif prefix == '+': |
| l_add += 1 |
| resultado_diff_html.append(f"<div class='linha-adicionada'><ins>{texto_escaped}</ins></div>") |
| if any(kw in texto.lower() for kw in kw_add_importante): |
| criticas.append(f"<span style='color:green;'>🟢 ADICIONADO (Importante):</span> {texto_escaped[:100]}{'...' if len(texto_escaped)>100 else ''}") |
| elif prefix == '?': |
| pass |
|
|
| matcher = difflib.SequenceMatcher(None, texto_original, texto_corrigido) |
| similarity_ratio = matcher.ratio() |
|
|
| resumo = (f"**📊 Resumo das Mudanças:**\n" |
| f"* Similaridade: {similarity_ratio:.1%}\n" |
| f"* Linhas Iguais: {l_igual}\n" |
| f"* Linhas Removidas: <span style='color:red;'>{l_rem}</span>\n" |
| f"* Linhas Adicionadas: <span style='color:green;'>{l_add}</span>\n" |
| f"* **Saldo:** {l_add - l_rem:+}") |
|
|
| crit_txt = "### 🔑 Mudanças Chave Detectadas:\n" + "\n".join([f"* {c}" for c in criticas]) if criticas else "Nenhuma mudança chave (baseada em keywords) detectada." |
|
|
| css = """ |
| <style> |
| .diff-container { |
| font-family: 'Courier New', Courier, monospace; |
| white-space: pre-wrap; |
| border: 1px solid #ccc; |
| padding: 15px; |
| background-color: #f9f9f9; |
| font-size: 0.9em; |
| line-height: 1.4; |
| border-radius: 5px; |
| max-height: 600px; |
| overflow-y: auto; |
| } |
| .linha-igual { color: #555; display: block; } |
| .linha-removida { background-color: #ffeef0; color: #a82d3a; display: block; } |
| .linha-adicionada { background-color: #e6ffed; color: #1d7a3a; display: block; } |
| .linha-removida del, .linha-adicionada ins { text-decoration: none; padding: 1px 3px; border-radius: 3px; display: inline-block; } |
| .linha-removida del { text-decoration: line-through; } |
| .linha-adicionada ins { font-weight: bold; } |
| </style> |
| """ |
| html_content = ''.join(resultado_diff_html) |
| if not html_content: |
| html_content = "<p><i>Nenhuma diferença visual a ser exibida.</i></p>" |
|
|
| html_output = f"{css}<div class='diff-container'>{html_content}</div>" |
|
|
| return html_output, resumo, crit_txt |
|
|
| def calcular_progresso(): |
| if not HISTORICO: return "📊 Progresso vazio. Nenhuma correção no histórico ainda." |
|
|
| notas_geral = [] |
| tipos_geral = set() |
| count_geral = 0 |
|
|
| for entry in HISTORICO: |
| if entry.get("modo") == "Geral IA" and "analise_formatada" in entry and "tipo_peticao" in entry: |
| count_geral += 1 |
| tipos_geral.add(entry["tipo_peticao"]) |
| nota_pattern = r"\*\*?(\d{1,2}[,.]\d?)\s*/\s*10\*\*?" |
| match = re.search(nota_pattern, entry["analise_formatada"]) |
| if match: |
| try: |
| nota = float(match.group(1).replace(",", ".")) |
| notas_geral.append(nota) |
| except (ValueError, TypeError): |
| logger.warning(f"Não foi possível converter nota '{match.group(1)}' para float no histórico.") |
|
|
|
|
| if count_geral == 0: |
| return "📊 Nenhuma correção no modo 'Geral IA' encontrada no histórico." |
|
|
| media_geral = sum(notas_geral) / len(notas_geral) if notas_geral else 0 |
| tipos_str = ", ".join(sorted(list(tipos_geral))) if tipos_geral else "Nenhum" |
|
|
| prog_txt = (f"**📊 Progresso (Modo Geral IA):**\n" |
| f"* **Correções Realizadas:** {count_geral}\n" |
| f"* **Tipos de Peça Corrigidos:** {tipos_str}\n" |
| f"* **Média das Notas:** {media_geral:.1f}/10 (Baseado em {len(notas_geral)} notas encontradas)") |
| return prog_txt |
|
|
| def mostrar_historico(): |
| if not HISTORICO: return "📜 Histórico vazio." |
|
|
| hist_md = "### 📜 Histórico Recente (Últimas 10 Correções)\n\n---\n" |
| for i, entry in enumerate(reversed(HISTORICO)): |
| entry_index = len(HISTORICO) - i |
| modo = entry.get("modo", "Desconhecido") |
| data_hora = entry.get("data", "Data Indisponível") |
| tipo = entry.get("tipo_peticao", "Tipo Indisponível") |
| caso_preview = entry.get("caso", "")[:80].strip() + "..." if entry.get("caso") else "[Caso não disponível]" |
|
|
| nota_str = "N/A" |
| analise_formatada = entry.get("analise_formatada", "") |
| if analise_formatada: |
| nota_pattern = r"\*\*?(\d{1,2}[,.]\d?)\s*/\s*10\*\*?" |
| match = re.search(nota_pattern, analise_formatada) |
| if match: |
| nota_num_str = match.group(1).replace(',', '.') |
| nota_str = f"{nota_num_str}/10" |
|
|
| hist_md += f"**{entry_index}. {data_hora} ({modo})**\n" |
| hist_md += f"* **Tipo:** {tipo}\n" |
| if modo == "Geral IA": |
| hist_md += f"* **Nota (IA):** {nota_str}\n" |
| hist_md += f"* **Caso:** _{caso_preview}_\n" |
| hist_md += "---\n" |
|
|
| return hist_md |
|
|
| def salvar_progresso(caso, peticao, tipo_peticao): |
| logger.info("Tentando salvar inputs atuais...") |
| if not caso or not caso.strip(): return "", "⚠️ Caso está vazio. Não é possível salvar.", None |
| if not peticao or not peticao.strip(): return "", "⚠️ Petição está vazia. Não é possível salvar.", None |
| if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO: return "", f"⚠️ Tipo de peça '{tipo_peticao}' é inválido. Não é possível salvar.", None |
|
|
| try: |
| dados = { |
| "tipo_peticao": tipo_peticao, |
| "caso": caso, |
| "peticao": peticao, |
| "timestamp_salvo": datetime.now().isoformat() |
| } |
| json_output = json.dumps(dados, ensure_ascii=False, indent=4) |
|
|
| ts_filename = datetime.now().strftime("%Y%m%d_%H%M%S") |
| tipo_peticao_sanitized = re.sub(r'\W+', '_', tipo_peticao).lower() |
| filename = f"oab_inputs_{tipo_peticao_sanitized}_{ts_filename}.json" |
| filepath = os.path.join(tempfile.gettempdir(), filename) |
|
|
| with open(filepath, 'w', encoding='utf-8') as f: |
| f.write(json_output) |
|
|
| logger.info(f"Inputs salvos com sucesso em: {filepath}") |
| msg = f"✅ Inputs salvos! Você pode baixar o arquivo `{filename}`." |
| return json_output, msg, filepath |
|
|
| except TypeError as te: |
| logger.error(f"Erro de tipo ao serializar para JSON: {str(te)}", exc_info=True) |
| return "", f"❌ Erro de tipo ao salvar (serialização JSON): {str(te)}", None |
| except IOError as ioe: |
| logger.error(f"Erro de I/O ao salvar arquivo: {str(ioe)}", exc_info=True) |
| return "", f"❌ Erro de I/O ao salvar arquivo: {str(ioe)}", None |
| except Exception as e: |
| logger.error(f"Erro inesperado ao salvar inputs: {str(e)}", exc_info=True) |
| return "", f"❌ Erro inesperado ao salvar: {str(e)}", None |
|
|
| def carregar_progresso(arquivo_entrada): |
| logger.info("Tentando carregar inputs de arquivo...") |
| if arquivo_entrada is None: |
| return "", "", "", "⚠️ Nenhum arquivo selecionado para carregar." |
|
|
| file_path = getattr(arquivo_entrada, 'name', None) |
| original_filename = getattr(arquivo_entrada, 'orig_name', file_path) |
| logger.info(f"Carregando do arquivo: {original_filename} (path: {file_path})") |
|
|
|
|
| if not file_path: |
| return "", "", "", "❌ Erro interno: Caminho do arquivo não encontrado." |
|
|
| try: |
| with open(file_path, 'r', encoding='utf-8') as f: |
| dados = json.load(f) |
|
|
| caso = dados.get("caso") |
| peticao = dados.get("peticao") |
| tipo = dados.get("tipo_peticao") |
| timestamp_salvo = dados.get("timestamp_salvo", "N/A") |
|
|
| if not isinstance(caso, str) or not caso.strip(): |
| return "", "", "", f"❌ Arquivo '{original_filename}' inválido: campo 'caso' ausente, vazio ou não é texto." |
| if not isinstance(peticao, str) or not peticao.strip(): |
| return caso, "", tipo, f"⚠️ Arquivo '{original_filename}' carregado, mas campo 'peticao' ausente ou vazio." |
| if not isinstance(tipo, str) or tipo not in TIPOS_DE_PETICAO: |
| return caso, peticao, "", f"⚠️ Tipo de peça '{tipo}' no arquivo '{original_filename}' não é mais válido ou está ausente. Verifique e selecione manualmente." |
|
|
| logger.info(f"Inputs carregados com sucesso de '{original_filename}' (Salvo em: {timestamp_salvo}).") |
| return tipo, caso, peticao, f"✅ Inputs carregados de '{original_filename}'!" |
|
|
| except FileNotFoundError: |
| logger.error(f"Erro ao carregar: Arquivo não encontrado em {file_path}") |
| return "", "", "", f"❌ Erro: Arquivo '{original_filename}' não encontrado no caminho esperado." |
| except json.JSONDecodeError: |
| logger.error(f"Erro ao carregar: Arquivo '{original_filename}' não contém JSON válido.") |
| return "", "", "", f"❌ Erro: Arquivo '{original_filename}' não é um JSON válido." |
| except KeyError as ke: |
| logger.error(f"Erro ao carregar: Campo obrigatório ausente no JSON '{original_filename}': {ke}") |
| return "", "", "", f"❌ Erro: Arquivo '{original_filename}' inválido (campo '{ke}' ausente)." |
| except Exception as e: |
| logger.error(f"Erro inesperado ao carregar de '{original_filename}': {str(e)}", exc_info=True) |
| return "", "", "", f"❌ Erro inesperado ao carregar '{original_filename}': {str(e)}" |
|
|
|
|
| def gerar_caso_ficticio(tipo_peticao="Petição Inicial"): |
| if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO: |
| logger.error(f"Tentativa de gerar caso com tipo inválido: {tipo_peticao}") |
| return f"⚠️ Erro: Tipo de peça '{tipo_peticao}' inválido. Selecione um tipo válido." |
|
|
| logger.info(f"Iniciando geração de caso fictício para: {tipo_peticao}") |
| prompt = estruturar_prompt_geracao_caso(tipo_peticao) |
| processing_log = ["⏳ Enviando pedido de geração de caso para a IA..."] |
|
|
| caso_bruto = chamar_api(prompt, use_oab_config=False) |
|
|
| if caso_bruto.startswith("❌") or caso_bruto.startswith("⏱️") or caso_bruto.startswith("⚠️"): |
| logger.error(f"Falha API ao gerar caso: {caso_bruto}") |
| return f"❌ **Falha ao gerar caso fictício pela IA:**\n{caso_bruto}" |
|
|
| logger.info("Caso fictício (bruto) recebido da API.") |
| processing_log.append("✅ Caso fictício recebido.") |
|
|
| caso_final = caso_bruto.strip() |
|
|
| situacao_problema_pattern = r"\*\*Situação-Problema:\*\*?\s*Na qualidade de advogado\(a\)\s*d[eo]\s*(.*?),\s*elabore a peça processual cabível\s*\(?" + re.escape(tipo_peticao) + r"\)?\s*\.?" |
| match = re.search(situacao_problema_pattern, caso_final, re.IGNORECASE | re.DOTALL) |
|
|
| if not match: |
| logger.warning("Linha 'Situação-Problema' padrão não encontrada ou mal formatada no caso gerado. Tentando adicionar...") |
| processing_log.append("⚠️ Linha 'Situação-Problema' não encontrada, adicionando...") |
| nomes_match = re.findall(r"\b([A-Z][a-z]+(?:\s+[A-Z][a-z]+)+)\b", caso_final) |
| cliente = nomes_match[0] if nomes_match else "[Nome do Cliente]" |
| caso_final += f"\n\n**Situação-Problema:** Na qualidade de advogado(a) de {cliente}, elabore a peça processual cabível ({tipo_peticao})." |
| else: |
| cliente_extraido = match.group(1).strip() |
| linha_padrao = f"**Situação-Problema:** Na qualidade de advogado(a) de {cliente_extraido}, elabore a peça processual cabível ({tipo_peticao})." |
| caso_final = re.sub(situacao_problema_pattern, linha_padrao, caso_final, flags=re.IGNORECASE | re.DOTALL) |
| processing_log.append("✅ Linha 'Situação-Problema' verificada/formatada.") |
|
|
| logger.info("Caso fictício gerado e pós-processado.") |
| return caso_final |
|
|
|
|
| def fornecer_dica_contextual(texto_peticao, tipo_peticao): |
| if not texto_peticao or not texto_peticao.strip(): |
| return "💡 Digite ou cole o texto da sua petição acima para receber dicas contextuais aqui..." |
| if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO: |
| return "⚠️ Selecione o **Tipo de Peça** acima para receber dicas mais relevantes." |
|
|
| dicas_relevantes = [] |
| texto_lower = texto_peticao.lower() |
|
|
| general_tips_keys = ["Clareza e Concisão", "Fundamentação Legal", "Coerência Argumentativa", "Revisão Ortográfica e Gramatical", "Formatação"] |
| for key in general_tips_keys: |
| if key in DICAS_OAB_MELHORADO: |
| dicas_relevantes.append(f"💡 **{key}:** {DICAS_OAB_MELHORADO[key]}") |
|
|
| keywords_gerais = { |
| "endereçamento": ["Endereçamento (Inicial)", "Reclamação Trabalhista (Endereçamento)"], |
| "qualifica": ["Qualificação (Inicial)"], |
| "fatos": ["Fatos (Inicial)"], |
| "fundamento": ["Fundamentação Legal", "Coerência Argumentativa", "Mandado de Segurança (Fundamentação)"], |
| "pedido": ["Pedidos (Inicial)", "Pedidos (Contestação)", "Reclamação Trabalhista (Pedidos)"], |
| "valor da causa": ["Valor da Causa (Inicial)"], |
| "custas": ["Custas (Inicial)", "Pressupostos Recursais", "Recurso Ordinário (Pressupostos)"], |
| "gratuidade": ["Custas (Inicial)"], |
| "audiência": ["Opção pela Audiência"], |
| "preliminar": ["Preliminares (Contestação)"], |
| "impugn": ["Impugnação Específica (Contestação)"], |
| "improcedência": ["Pedidos (Contestação)"], |
| "recurso": ["Pressupostos Recursais", "Dialeticidade (Recurso)", "Delimitação da Matéria (Recurso)", "Recurso Ordinário (Pressupostos)"], |
| "tutela": ["Petição de Tutela Provisória", "Fumus Boni Iuris", "Periculum in Mora", "Reversibilidade"], |
| "liminar": ["Petição de Tutela Provisória", "Periculum in Mora", "Mandado de Segurança (Fundamentação)"], |
| "reclamação": ["Reclamação Trabalhista (Endereçamento)", "Reclamação Trabalhista (Pedidos)"], |
| "verbas rescisórias": ["Reclamação Trabalhista (Pedidos)"], |
| "mandado de segurança": ["Mandado de Segurança (Fundamentação)", "Mandado de Segurança (Cabimento)"], |
| "ato coator": ["Mandado de Segurança (Fundamentação)"], |
| "direito líquido e certo": ["Mandado de Segurança (Fundamentação)"], |
| } |
|
|
| found_tips_keys = set(general_tips_keys) |
| for keyword, dica_keys in keywords_gerais.items(): |
| if keyword in texto_lower: |
| for key in dica_keys: |
| if key in DICAS_OAB_MELHORADO and key not in found_tips_keys: |
| dicas_relevantes.append(f"🔑 **{key} (Detectado por '{keyword}'):** {DICAS_OAB_MELHORADO[key]}") |
| found_tips_keys.add(key) |
|
|
| tipo_simples = tipo_peticao.split()[0].lower() if tipo_peticao else "" |
| for chave, dica in DICAS_OAB_MELHORADO.items(): |
| if f"({tipo_simples})" in chave.lower(): |
| if chave not in found_tips_keys: |
| dicas_relevantes.append(f"📌 **{chave} (Específico para {tipo_peticao}):** {dica}") |
| found_tips_keys.add(chave) |
|
|
| if not dicas_relevantes: |
| return "💡 Continue escrevendo ou selecione o Tipo de Peça para dicas mais específicas." |
|
|
| max_tips = 7 |
| output_dicas = "### ✨ Dicas Contextuais:\n\n" + "\n\n".join(dicas_relevantes[:max_tips]) |
| if len(dicas_relevantes) > max_tips: |
| output_dicas += "\n\n*(Mais dicas podem ser aplicáveis...)*" |
|
|
| return output_dicas |
|
|
| def estruturar_prompt_geracao_caso(tipo_peticao="Petição Inicial"): |
| prompt = f""" |
| Crie um caso fictício **detalhado e realista** para um exercício prático de elaboração de uma peça processual do tipo **"{tipo_peticao}"**, simulando um cenário típico de 2ª Fase do Exame de Ordem (OAB). |
| |
| **Instruções Gerais para o Caso:** |
| 1. **Tamanho Ideal:** Entre 15 a 30 linhas de texto. |
| 2. **Detalhes Essenciais:** Inclua nomes completos fictícios para as partes, datas relevantes (dia, mês, ano), locais (cidades/estados fictícios ou genéricos como "Cidade Alfa"), valores monetários (se aplicável), a natureza da relação jurídica entre as partes, e o cerne do conflito/lide. |
| 3. **Coerência Fática e Jurídica:** Os fatos devem ser lógicos, consistentes e levar naturalmente à necessidade de elaboração da peça do tipo "{tipo_peticao}". |
| 4. **Foco na Peça Solicitada:** O problema deve claramente indicar que a peça a ser elaborada é uma **{tipo_peticao}**. |
| 5. **Informações Suficientes (e algumas omissões):** Forneça dados suficientes para a elaboração da peça, mas omita dados secundários (ex: RG/CPF, e-mail) para que o aluno precise usar "XXX". |
| |
| **Instruções Específicas para Gerar Caso para "{tipo_peticao}":** |
| """ |
| if tipo_peticao == "Reclamação Trabalhista": |
| prompt += "* **Área:** Direito do Trabalho. **Cenário:** Crie uma situação de um empregado (seu cliente) que foi dispensado sem justa causa e não recebeu corretamente suas verbas rescisórias. Inclua detalhes como data de admissão e demissão, último salário, função, jornada de trabalho (com indicação de horas extras não pagas) e a recusa do empregador em pagar o que é devido." |
| elif tipo_peticao == "Contestação Trabalhista": |
| prompt += "* **Área:** Direito do Trabalho. **Cenário:** Seu cliente, uma empresa, recebeu uma notificação de Reclamação Trabalhista. Resuma os pedidos do reclamante (ex: horas extras, adicional de insalubridade). Apresente a versão da empresa, que contradiz as alegações (ex: o empregado tinha cargo de confiança, a empresa fornecia EPIs, etc.), fornecendo elementos para a defesa de mérito e, se possível, para uma prejudicial de mérito como a prescrição quinquenal." |
| elif tipo_peticao == "Recurso Ordinário": |
| prompt += "* **Área:** Direito do Trabalho. **Cenário:** Foi proferida uma sentença parcialmente procedente em uma Reclamação Trabalhista, e seu cliente (reclamante ou reclamada) foi sucumbente em um ponto específico e relevante. **Descreva o ponto da sentença que foi desfavorável** e o fundamento do juiz. Apresente os argumentos jurídicos para a reforma dessa parte específica da decisão pelo TRT." |
| elif tipo_peticao == "Mandado de Segurança": |
| prompt += "* **Área:** Direito Constitucional/Administrativo. **Cenário:** Crie uma situação em que seu cliente (pessoa física ou jurídica) teve um direito líquido e certo violado por um ato ilegal ou com abuso de poder de uma autoridade pública (ex: recusa indevida de uma licença, anulação de um ato administrativo benéfico sem o devido processo legal, retenção de mercadoria em alfândega sem justificativa). O caso deve ter prova pré-constituída clara e não deve caber outro recurso com efeito suspensivo." |
| elif tipo_peticao == "Habeas Data": |
| prompt += "* **Área:** Direito Constitucional. **Cenário:** Seu cliente tentou obter informações pessoais a seu respeito constantes em um banco de dados de entidade governamental (ex: informações sobre seu histórico em um órgão de inteligência, dados sobre pontuação em um concurso público) e teve seu pedido negado administrativamente sem justificativa legal, ou o prazo para resposta se esgotou. O objetivo é apenas o acesso ou a retificação dessas informações." |
| elif tipo_peticao == "Ação Popular": |
| prompt += "* **Área:** Direito Constitucional. **Cenário:** Seu cliente, um cidadão (lembre-se dos requisitos de legitimidade), descobriu um ato lesivo ao patrimônio público, à moralidade administrativa, ao meio ambiente ou ao patrimônio histórico e cultural, praticado por um agente público (ex: contratação de obra superfaturada sem licitação, nomeação de parente para cargo comissionado, permissão para desmatamento em área de preservação). O objetivo é a anulação do ato lesivo." |
| else: |
| prompt += f"* Crie um cenário jurídico cível ou empresarial claro e plausível que demande especificamente a elaboração de uma peça do tipo **{tipo_peticao}**." |
|
|
| prompt += """ |
| |
| **Finalização Obrigatória:** |
| Conclua o texto do caso **exatamente** com a seguinte frase, substituindo "[Nome do Cliente]" pelo nome do cliente fictício criado no caso: |
| "**Situação-Problema:** Na qualidade de advogado(a) de [Nome do Cliente], elabore a peça processual cabível ({tipo_peticao})." |
| """ |
| return prompt |
|
|
|
|
| def interface_gradio(): |
| with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="orange"), title="Corretor Avançado de Petições") as demo: |
| gr.Markdown("# ⚖️ Corretor Jurídico OAB Master ⚖️") |
| gr.Markdown("Ferramenta para auxiliar na prática da 2ª Fase da OAB. Use a **Correção Geral (IA)** para feedback amplo ou a **Correção Baseada no Padrão OAB** para avaliação específica de provas anteriores.") |
|
|
| peticao_original_state_geral = gr.State(value="") |
|
|
| with gr.Tabs() as main_tabs: |
| with gr.TabItem("🤖 Correção Geral (IA)", id=0): |
| with gr.Row(): |
| with gr.Column(scale=2): |
| gr.Markdown("### 1. Insira o Caso e Sua Peça") |
| tipo_peticao_input_geral = gr.Dropdown(label="Tipo de Peça Prática", choices=TIPOS_DE_PETICAO, value="Petição Inicial", info="Selecione o tipo de peça que você elaborou.") |
| with gr.Row(): |
| caso_input_geral = gr.Textbox(label="📋 Caso / Enunciado", lines=8, placeholder="Cole aqui o caso ou enunciado do problema...") |
| caso_btn_geral = gr.Button("🎲 Gerar Caso", scale=0, variant="secondary") |
|
|
| gr.Markdown("**Sua Peça Prática:**") |
| with gr.Tabs(): |
| with gr.TabItem("✏️ Digitar Texto"): |
| peticao_texto_input_geral = gr.Textbox(label=None, lines=12, placeholder="Digite ou cole aqui o texto completo da sua petição...") |
| dica_contextual_output_geral = gr.Markdown("*(Dicas aparecerão aqui enquanto você digita...)*") |
| peticao_texto_input_geral.change(lambda x: x, inputs=peticao_texto_input_geral, outputs=peticao_original_state_geral) |
|
|
| with gr.TabItem("📎 Anexar Arquivo"): |
| peticao_arquivo_input_geral = gr.File(label="Selecione .pdf, .docx ou .txt", file_types=[".pdf",".docx",".txt"], type="filepath") |
| arquivo_preview_output_geral = gr.Textbox(label="🔍 Status/Prévia Arquivo", lines=2, interactive=False) |
|
|
| btn_corrigir_geral = gr.Button("🚀 Analisar e Corrigir Peça (IA)", variant="primary", scale=2) |
|
|
| with gr.Column(scale=1): |
| gr.Markdown("### 2. Opções e Controles") |
| modo_treinamento_geral = gr.Checkbox(label="🎓 Modo Treinamento Detalhado", value=False, info="Fornece feedback mais aprofundado e exemplos de correção.") |
| with gr.Accordion("⚙️ Configurações Avançadas da IA", open=False): |
| temperatura_slider = gr.Slider(0.0, 1.0, value=CONFIG["temperatura"], step=0.1, label="Criatividade (Temperatura)", info="Valores menores = mais focado/determinístico; Valores maiores = mais criativo/variado.") |
| max_tokens_slider = gr.Slider(1024, 8192, value=CONFIG["max_tokens"], step=256, label="Tamanho Máx. Resposta (Tokens)", info="Limite máximo para o tamanho da resposta da IA.") |
| def atualizar_config_geral(temp, tokens): |
| CONFIG["temperatura"] = temp |
| CONFIG["max_tokens"] = tokens |
| logger.info(f"Config IA Geral atualizada: Temp={temp}, MaxTokens={tokens}") |
| return f"✅ Configs IA salvas (Temp={temp}, MaxTokens={tokens})" |
| config_update_btn_geral = gr.Button("💾 Salvar Configs IA") |
| config_status_geral = gr.Textbox(label="Status Config", interactive=False) |
| config_update_btn_geral.click( |
| atualizar_config_geral, |
| [temperatura_slider, max_tokens_slider], |
| config_status_geral |
| ) |
|
|
| with gr.Accordion("📊 Progresso e Histórico", open=False): |
| progresso_output = gr.Markdown("*(Clique para carregar)*") |
| progresso_btn = gr.Button("🔄 Ver Progresso (Notas IA)") |
| historico_output = gr.Markdown("*(Clique para carregar)*") |
| historico_btn = gr.Button("🔄 Ver Histórico (Últimas 10)") |
| progresso_btn.click(calcular_progresso, outputs=progresso_output) |
| historico_btn.click(mostrar_historico, outputs=historico_output) |
|
|
| with gr.Accordion("💾/📂 Salvar / Carregar Inputs", open=False): |
| gr.Markdown("Salve os textos do Caso e da Sua Peça para continuar depois.") |
| salvar_btn_geral = gr.Button("💾 Salvar Caso e Peça Atual") |
| progresso_download_file_geral = gr.File(label="Baixar Arquivo Salvo (.json)", interactive=False) |
| saveload_status_geral = gr.Textbox(label="Status Salvar/Carregar", interactive=False, scale=1) |
| gr.Markdown("Carregue um arquivo .json salvo anteriormente.") |
| arquivo_progresso_upload_geral = gr.File(label="Carregar Arquivo .json", file_types=[".json"], type="filepath") |
| carregar_btn_geral = gr.Button("📂 Carregar Caso e Peça") |
|
|
|
|
| gr.Markdown("---") |
| gr.Markdown("### 3. Resultados da Correção (IA)") |
| processing_message_geral = gr.Markdown("", visible=False) |
|
|
| with gr.Tabs(): |
| with gr.TabItem("📊 Análise Detalhada (IA)"): |
| resultado_analise_output_geral = gr.Markdown("*A análise da IA aparecerá aqui...*") |
| with gr.Row(): |
| export_analise_txt_btn = gr.Button("📄 Exportar Análise (TXT)") |
| export_analise_pdf_btn = gr.Button("📜Exportar Análise (PDF)") |
| download_analise_file = gr.File(label="Baixar Análise", interactive=False) |
|
|
| with gr.TabItem("✨ Peça Corrigida (Sugestão IA)"): |
| resultado_peticao_corrigida_output_geral = gr.Textbox(label="Texto da Peça Corrigida/Aprimorada pela IA:", lines=20, show_copy_button=True, interactive=True) |
| with gr.Row(): |
| export_correcao_txt_btn = gr.Button("📄 Exportar Correção (TXT)") |
| export_correcao_pdf_btn = gr.Button("📜Exportar Correção (PDF)") |
| download_correcao_file = gr.File(label="Baixar Correção", interactive=False) |
|
|
| with gr.TabItem("⚖️ Comparativo (Diff)"): |
| gr.Markdown("**Comparação entre sua peça original e a versão corrigida pela IA:**") |
| with gr.Row(): |
| with gr.Column(scale=1): |
| resumo_alteracoes_output_geral = gr.Markdown("*Resumo das alterações...*") |
| criticas_output_geral = gr.Markdown("*Mudanças chave...*") |
| with gr.Column(scale=3): |
| visualizacao_detalhada_output_geral = gr.HTML(value="<p><i>A visualização das diferenças (diff) aparecerá aqui após a correção...</i></p>") |
|
|
| with gr.TabItem("📄 Correção Padrão OAB", id=1): |
| gr.Markdown("Use esta aba para **corrigir sua peça** usando o **padrão de resposta oficial** de um Exame da OAB anterior, ou para **gerar uma peça modelo** com base nesse padrão.") |
| with gr.Row(): |
| with gr.Column(scale=1): |
| gr.Markdown("### 1. Material Oficial da OAB") |
| gr.Markdown("Forneça o Enunciado, Gabarito Comentado e Distribuição de Pontos.") |
| with gr.Tabs(): |
| with gr.TabItem("📎 Carregar PDF Oficial"): |
| oab_pdf_input = gr.File(label="Padrão de Resposta OAB (.pdf)", file_types=[".pdf"], type="filepath") |
| parse_status_output = gr.Textbox(label="Status Extração PDF", interactive=False, lines=2) |
| gr.Markdown("_O sistema tentará extrair as seções automaticamente do PDF._") |
| with gr.TabItem("✏️ Inserir Manualmente"): |
| oab_enunciado_input = gr.Textbox(label="Enunciado", lines=5, placeholder="Cole o Enunciado aqui...") |
| oab_gabarito_input = gr.Textbox(label="Gabarito Comentado / Padrão Resposta", lines=8, placeholder="Cole o Gabarito Comentado ou Padrão de Resposta aqui...") |
| oab_distribuicao_input = gr.Textbox(label="Distribuição dos Pontos", lines=8, placeholder="Cole a Distribuição dos Pontos aqui...") |
|
|
| with gr.Column(scale=1): |
| gr.Markdown("### 2. Ações e Sua Peça") |
| gr.Markdown("**Gerar Peça Modelo:**") |
| btn_gerar_modelo_oab = gr.Button("📝 Gerar Peça Modelo OAB (Baseado no Material Acima)") |
| gr.Markdown("---") |
| gr.Markdown("**Corrigir Sua Peça:**") |
| peticao_usuario_oab_input = gr.Textbox(label="Sua Peça Prática (para corrigir pela OAB)", lines=10, placeholder="Cole aqui a peça que você elaborou para este caso OAB...") |
| btn_corrigir_pela_oab = gr.Button("🎯 Corrigir Minha Peça (Conforme Padrão OAB)", variant="primary") |
|
|
|
|
| gr.Markdown("---") |
| gr.Markdown("### 3. Resultados (Padrão OAB)") |
| processing_message_oab = gr.Markdown("", visible=False) |
|
|
| with gr.Tabs(): |
| with gr.TabItem("📝 Peça Modelo OAB (Gerada)"): |
| resultado_modelo_oab_output = gr.Textbox(label="Texto da Peça Modelo Gerada pela IA:", lines=20, show_copy_button=True, interactive=True) |
| with gr.Row(): |
| export_modelo_txt_btn = gr.Button("📄 Exportar Modelo (TXT)") |
| export_modelo_pdf_btn = gr.Button("📜 Exportar Modelo (PDF)") |
| download_modelo_file = gr.File(label="Baixar Modelo", interactive=False) |
|
|
| with gr.TabItem("🎯 Avaliação da Sua Peça (Conforme OAB)"): |
| resultado_correcao_oab_output = gr.Markdown("*A avaliação da sua peça conforme o padrão OAB aparecerá aqui...*") |
| with gr.Row(): |
| export_avaliacao_txt_btn = gr.Button("📄Exportar Avaliação (TXT)") |
| export_avaliacao_pdf_btn = gr.Button("📜Exportar Avaliação (PDF)") |
| download_avaliacao_file = gr.File(label="Baixar Avaliação", interactive=False) |
|
|
| clear_btn = gr.Button("🗑️ Limpar Todos os Campos", variant="stop") |
|
|
|
|
| caso_btn_geral.click( |
| gerar_caso_ficticio, |
| inputs=[tipo_peticao_input_geral], |
| outputs=[caso_input_geral] |
| ) |
|
|
| peticao_texto_input_geral.change( |
| fornecer_dica_contextual, |
| inputs=[peticao_texto_input_geral, tipo_peticao_input_geral], |
| outputs=dica_contextual_output_geral, |
| ) |
| tipo_peticao_input_geral.change( |
| fornecer_dica_contextual, |
| inputs=[peticao_texto_input_geral, tipo_peticao_input_geral], |
| outputs=dica_contextual_output_geral, |
| ) |
|
|
| peticao_arquivo_input_geral.change( |
| process_file, |
| inputs=[peticao_arquivo_input_geral], |
| outputs=[arquivo_preview_output_geral] |
| ) |
|
|
| btn_corrigir_geral.click( |
| lambda: gr.update(value="⏳ Processando Análise e Correção (IA)... Pode levar alguns instantes.", visible=True), |
| outputs=processing_message_geral |
| ).then( |
| corrigir_peticao_geral_ia, |
| inputs=[caso_input_geral, tipo_peticao_input_geral, peticao_texto_input_geral, peticao_arquivo_input_geral, modo_treinamento_geral], |
| outputs=[ |
| resultado_analise_output_geral, |
| peticao_original_state_geral, |
| resultado_peticao_corrigida_output_geral, |
| resumo_alteracoes_output_geral, |
| criticas_output_geral, |
| gr.Markdown(visible=False), |
| visualizacao_detalhada_output_geral |
| ] |
| ).then( |
| lambda: gr.update(visible=False), outputs=processing_message_geral |
| ) |
|
|
| salvar_btn_geral.click( |
| lambda: gr.update(value="⏳ Salvando...", visible=True), outputs=processing_message_geral |
| ).then( |
| salvar_progresso, |
| inputs=[caso_input_geral, peticao_texto_input_geral, tipo_peticao_input_geral], |
| outputs=[gr.State(None), saveload_status_geral, progresso_download_file_geral] |
| ).then( |
| lambda x: gr.update(visible=bool(x)), inputs=[progresso_download_file_geral], outputs=[progresso_download_file_geral] |
| ).then( |
| lambda: gr.update(visible=False), outputs=processing_message_geral |
| ) |
|
|
| carregar_btn_geral.click( |
| lambda: gr.update(value="⏳ Carregando...", visible=True), outputs=processing_message_geral |
| ).then( |
| carregar_progresso, |
| inputs=[arquivo_progresso_upload_geral], |
| outputs=[tipo_peticao_input_geral, caso_input_geral, peticao_texto_input_geral, saveload_status_geral] |
| ).then( |
| lambda: gr.update(visible=False), outputs=processing_message_geral |
| ) |
|
|
| oab_pdf_input.change( |
| parse_oab_pdf, |
| inputs=[oab_pdf_input], |
| outputs=[oab_enunciado_input, oab_gabarito_input, oab_distribuicao_input, parse_status_output] |
| ) |
|
|
| btn_gerar_modelo_oab.click( |
| lambda: gr.update(value="⏳ Gerando Peça Modelo OAB (IA)...", visible=True), outputs=processing_message_oab |
| ).then( |
| gerar_peticao_modelo_oab, |
| inputs=[oab_enunciado_input, oab_gabarito_input, oab_distribuicao_input], |
| outputs=[resultado_modelo_oab_output] |
| ).then( |
| lambda: gr.update(visible=False), outputs=processing_message_oab |
| ) |
|
|
| btn_corrigir_pela_oab.click( |
| lambda: gr.update(value="⏳ Corrigindo sua peça conforme Padrão OAB (IA)...", visible=True), outputs=processing_message_oab |
| ).then( |
| corrigir_pela_oab, |
| inputs=[peticao_usuario_oab_input, oab_enunciado_input, oab_gabarito_input, oab_distribuicao_input], |
| outputs=[resultado_correcao_oab_output] |
| ).then( |
| lambda: gr.update(visible=False), outputs=processing_message_oab |
| ) |
|
|
| export_analise_txt_btn.click(export_to_txt, inputs=[resultado_analise_output_geral, gr.State("analise_ia")], outputs=download_analise_file) |
| export_analise_pdf_btn.click(export_to_pdf, inputs=[resultado_analise_output_geral, gr.State("analise_ia")], outputs=download_analise_file) |
| export_correcao_txt_btn.click(export_to_txt, inputs=[resultado_peticao_corrigida_output_geral, gr.State("correcao_ia")], outputs=download_correcao_file) |
| export_correcao_pdf_btn.click(export_to_pdf, inputs=[resultado_peticao_corrigida_output_geral, gr.State("correcao_ia")], outputs=download_correcao_file) |
| export_modelo_txt_btn.click(export_to_txt, inputs=[resultado_modelo_oab_output, gr.State("modelo_oab")], outputs=download_modelo_file) |
| export_modelo_pdf_btn.click(export_to_pdf, inputs=[resultado_modelo_oab_output, gr.State("modelo_oab")], outputs=download_modelo_file) |
| export_avaliacao_txt_btn.click(export_to_txt, inputs=[resultado_correcao_oab_output, gr.State("avaliacao_oab")], outputs=download_avaliacao_file) |
| export_avaliacao_pdf_btn.click(export_to_pdf, inputs=[resultado_correcao_oab_output, gr.State("avaliacao_oab")], outputs=download_avaliacao_file) |
|
|
| all_inputs_outputs = [ |
| tipo_peticao_input_geral, modo_treinamento_geral, caso_input_geral, |
| peticao_texto_input_geral, peticao_arquivo_input_geral, arquivo_preview_output_geral, |
| dica_contextual_output_geral, saveload_status_geral, arquivo_progresso_upload_geral, |
| progresso_download_file_geral, |
| processing_message_geral, resultado_analise_output_geral, resultado_peticao_corrigida_output_geral, |
| resumo_alteracoes_output_geral, criticas_output_geral, visualizacao_detalhada_output_geral, |
| download_analise_file, download_correcao_file, |
| oab_pdf_input, parse_status_output, oab_enunciado_input, oab_gabarito_input, |
| oab_distribuicao_input, peticao_usuario_oab_input, |
| processing_message_oab, resultado_modelo_oab_output, resultado_correcao_oab_output, |
| download_modelo_file, download_avaliacao_file, |
| config_status_geral, progresso_output, historico_output, |
| peticao_original_state_geral |
| ] |
| clear_values = [ |
| "Petição Inicial", False, "", |
| "", None, "", |
| "*(Dicas aparecerão aqui...)*", "", None, None, |
| "", "", "", |
| "", "", "<p><i>(Diff será exibido aqui)</i></p>", |
| None, None, |
| None, "", "", "", "", "", |
| "", "", "", |
| None, None, |
| "", "", "", |
| "" |
| ] |
|
|
| clear_btn.click( |
| fn=lambda: clear_values, |
| inputs=None, |
| outputs=all_inputs_outputs |
| ) |
|
|
| return demo |
|
|
| if __name__ == "__main__": |
| logger.info("Iniciando a aplicação Gradio Corretor Jurídico OAB Master...") |
| try: |
| app = interface_gradio() |
| app.launch(debug=False, share=False) |
| logger.info("Aplicação Gradio encerrada.") |
| except Exception as e: |
| logger.exception("Erro fatal ao iniciar ou executar a aplicação Gradio.") |
| print(f"ERRO FATAL: {e}") |