diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -30,34 +30,73 @@ 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.") -GEMINI_API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={GEMINI_API_KEY}" +# Use um modelo mais recente se disponível e adequado, como gemini-1.5-flash +# GEMINI_API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key={GEMINI_API_KEY}" +# Se gemini-1.5-flash não estiver disponível ou causar problemas, volte para gemini-pro ou o que funcionava +# GEMINI_API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key={GEMINI_API_KEY}" +GEMINI_API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key={GEMINI_API_KEY}" + CONFIG = { "max_retry_attempts": 3, "retry_delay": 5, - "timeout": 60, - "temperatura": 0.2, - "max_tokens": 12882 + "timeout": 120, # Aumentar timeout para prompts mais complexos + "temperatura": 0.3, # Um pouco mais de criatividade pode ser útil + "max_tokens": 8192 # Ajustar conforme necessidade e limites do modelo } -rate_limiter = RateLimiter(max_calls=60, period=60) - +rate_limiter = RateLimiter(max_calls=30, period=60) # Reduzir um pouco a taxa para evitar 429 + +# --- TIPOS DE PEÇAS PROCESSUAIS --- +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" +] + +# --- DICIONÁRIO DE DICAS (Pode ser expandido por tipo) --- DICAS_OAB_MELHORADO = { - "Endereçamento": "Em relações de consumo, prefira o foro do domicílio do autor (Art. 101, I, CDC). Exemplo: 'Excelentíssimo Senhor Juiz de Direito da Vara Cível da Comarca de [Cidade do Autor]'.", - "Qualificação": "Mencione nome, estado civil (se informado), endereço (use 'Rua XXX' se não fornecido). Dica: Verifique se todos os dados estão corretos.", - "Fatos": "Descreva os fatos cronologicamente, sem inventar informações. Exemplo: 'Em [data], o autor celebrou contrato com o réu para [descrição]'.", - "Pedidos": "Seja específico nos pedidos (ex.: 'condenação ao pagamento de R$ X'). Inclua danos morais e materiais, se aplicável.", - "Custas": "Inclua 'Requer a juntada da guia de custas devidamente recolhida'. Nunca omita para evitar extinção do processo.", - "Fundamentação": "Baseie-se em artigos do CPC ou CDC e súmulas do STJ (ex.: Súmula 387 para danos morais).", - "Fechamento": "Finalize com 'Nestes termos pede deferimento', sem vírgula. Evite erros de pontuação.", - "Estratégia Jurídica": "Utilize precedentes e súmulas para fortalecer a argumentação.", - "Provas": "Especifique as provas a produzir. Inclua testemunhas, documentos e perícias, se necessário.", - "Contra-argumentação": "Antecipe defesas da parte contrária e prepare respostas." + # Dicas Gerais (aplicáveis a várias peças) + "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.", + + # Dicas Específicas (Exemplos para Petição Inicial) + "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).", + + # Dicas Específicas (Exemplos para Contestação) + "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.", + + # Dicas Específicas (Exemplo para Recurso) + "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.", + + # Dicas Específicas (Tutela Provisória) + "Fumus Boni Iuris": "Demonstre a plausibilidade do direito alegado com base em provas e argumentos sólidos.", + "Periculum in Mora": "Evidencie o risco de dano irreparável ou de difícil reparação se a medida não for concedida imediatamente.", + "Reversibilidade": "Se possível, argumente que a medida é reversível caso a decisão final seja diferente (Art. 300, §3º, CPC).", } HISTORICO = [] CORRECAO_CACHE = {} +# --- Funções de Extração de Texto (sem alterações significativas) --- def preprocess_image_for_ocr(img): try: gray = img.convert('L') @@ -66,744 +105,1475 @@ def preprocess_image_for_ocr(img): 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)}" + return f"❌ Erro no pré-processamento: {str(e)}" # Retorna o erro def extract_text_from_pdf(file_path_or_obj): + text = "" + ocr_text = "" try: - if isinstance(file_path_or_obj, str): - with open(file_path_or_obj, 'rb') as f: - text = "" - with pdfplumber_open(f) as pdf: - for page in pdf.pages: - text += (page.extract_text() or "") + "\n" - if not text.strip(): - images = pdfplumber_open(f).pages - for i, image in enumerate(images): - try: - img = image.to_image() - preprocessed_img = preprocess_image_for_ocr(img) - text += pytesseract.image_to_string(preprocessed_img) + "\n" - except Exception as e: - text += f"[Erro na página {i+1}: {str(e)}]\n" + pdf_reader = pdfplumber_open(file_path_or_obj) + for i, page in enumerate(pdf_reader.pages): + page_text = page.extract_text() + if page_text: + text += page_text + "\n" + else: # Tenta OCR se texto extraído for pobre/vazio + logger.warning(f"Página {i+1} sem texto extraível, tentando OCR.") + try: + img = page.to_image(resolution=300) # Aumenta resolução para OCR + preprocessed_img = preprocess_image_for_ocr(img.original) + page_ocr_text = pytesseract.image_to_string(preprocessed_img, lang='por') # Especifica português + if page_ocr_text.strip(): + ocr_text += page_ocr_text + "\n" + else: + logger.warning(f"OCR falhou na página {i+1}.") + ocr_text += f"[OCR falhou na página {i+1}]\n" + except Exception as ocr_e: + logger.error(f"Erro no OCR da página {i+1}: {str(ocr_e)}") + ocr_text += f"[Erro no OCR da página {i+1}: {str(ocr_e)}]\n" + pdf_reader.close() + + # Decide qual texto retornar + if text.strip() and len(text) > len(ocr_text) * 0.5: # Se texto extraído parece razoável + logger.info("Usando texto extraído do PDF.") + return text + elif ocr_text.strip(): + logger.info("Usando texto do OCR do PDF.") + return ocr_text else: - text = "" - with pdfplumber_open(file_path_or_obj) as pdf: - for page in pdf.pages: - text += (page.extract_text() or "") + "\n" - if not text.strip(): - images = pdfplumber_open(file_path_or_obj).pages - for i, image in enumerate(images): - try: - img = image.to_image() - preprocessed_img = preprocess_image_for_ocr(img) - text += pytesseract.image_to_string(preprocessed_img) + "\n" - except Exception as e: - text += f"[Erro na página {i+1}: {str(e)}]\n" - return text if text.strip() else "⚠️ Não foi possível extrair texto do PDF." + logger.warning("Não foi possível extrair texto significativo do PDF.") + return "⚠️ Não foi possível extrair texto do PDF (tentativa de OCR falhou ou texto inexistente)." + except Exception as e: - logger.error(f"Erro ao processar o PDF: {str(e)}") - return f"❌ Erro ao processar o PDF: {str(e)}" + logger.error(f"Erro geral ao processar o PDF: {str(e)}") + # Tenta reabrir e usar PyPDF2 como fallback básico se pdfplumber falhar + try: + logger.info("Tentando fallback com PyPDF2.") + text_fallback = "" + if isinstance(file_path_or_obj, str): + with open(file_path_or_obj, 'rb') as f: + reader = PyPDF2.PdfReader(f) + for page in reader.pages: + text_fallback += page.extract_text() or "" + else: + # Precisa resetar o ponteiro se for objeto de arquivo + file_path_or_obj.seek(0) + reader = PyPDF2.PdfReader(file_path_or_obj) + for page in reader.pages: + text_fallback += page.extract_text() or "" + + if text_fallback.strip(): + logger.info("Texto extraído com fallback PyPDF2.") + return text_fallback + else: + return f"❌ Erro ao processar o PDF e fallback falhou: {str(e)}" + except Exception as fallback_e: + logger.error(f"Erro no fallback com PyPDF2: {str(fallback_e)}") + return f"❌ Erro crítico ao processar o PDF: {str(e)}" + def extract_text_from_docx(file_path_or_obj): try: - if isinstance(file_path_or_obj, str): - with open(file_path_or_obj, 'rb') as f: - doc = docx.Document(f) - text = "" - for para in doc.paragraphs: - text += para.text + "\n" - for table in doc.tables: - for row in table.rows: - for cell in row.cells: - text += cell.text + " " - text += "\n" - else: - doc = docx.Document(file_path_or_obj) - text = "" - for para in doc.paragraphs: - text += para.text + "\n" - for table in doc.tables: - for row in table.rows: - for cell in row.cells: - text += cell.text + " " - text += "\n" - return text if text.strip() else "⚠️ Não foi possível extrair texto do DOCX." + doc = docx.Document(file_path_or_obj) # Funciona com path ou file object + text = "" + for para in doc.paragraphs: + text += para.text + "\n" + # Opcional: Extrair texto de tabelas + # for table in doc.tables: + # for row in table.rows: + # for cell in row.cells: + # text += cell.text + "\t" # Separar células com tab + # text += "\n" + return text if text.strip() else "⚠️ Documento DOCX vazio ou sem texto." except Exception as e: logger.error(f"Erro ao processar o DOCX: {str(e)}") return f"❌ Erro ao processar o DOCX: {str(e)}" def extract_text_from_txt(file_path_or_obj): try: + # Se for um path, abre o arquivo if isinstance(file_path_or_obj, str): with open(file_path_or_obj, 'rb') as f: content = f.read() - encodings = ['utf-8', 'latin-1', 'cp1252', 'iso-8859-1'] - for encoding in encodings: - try: - return content.decode(encoding) - except UnicodeDecodeError: - continue - return "❌ Não foi possível decodificar o arquivo TXT." - else: + # Se for um objeto de arquivo (como do Gradio), lê diretamente + elif hasattr(file_path_or_obj, 'read'): content = file_path_or_obj.read() - encodings = ['utf-8', 'latin-1', 'cp1252', 'iso-8859-1'] - for encoding in encodings: - try: - return content.decode(encoding) - except UnicodeDecodeError: - continue - return "❌ Não foi possível decodificar o arquivo TXT." + # Tenta resetar o ponteiro se possível, para reutilização + if hasattr(file_path_or_obj, 'seek'): + try: file_path_or_obj.seek(0) + except: pass + else: + return "❌ Tipo de entrada TXT inválido." + + # Tenta decodificar com encodings comuns + 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.") + return "❌ Não foi possível decodificar o arquivo TXT. Verifique o encoding." except Exception as e: logger.error(f"Erro ao processar o TXT: {str(e)}") return f"❌ Erro ao processar o TXT: {str(e)}" def process_file(file_obj): if file_obj is None: - return "Nenhum arquivo enviado." + logger.warning("Nenhum arquivo fornecido para processamento.") + return "⚠️ Nenhum arquivo enviado." try: - if hasattr(file_obj, 'name'): - file_path = file_obj.name - filename = os.path.basename(file_path).lower() - file_size = os.path.getsize(file_path) - if file_size > 10 * 1024 * 1024: - return "⚠️ O arquivo é muito grande. Máx. 10 MB." - if filename.endswith('.pdf'): - return extract_text_from_pdf(file_path) - elif filename.endswith('.docx'): - return extract_text_from_docx(file_path) - elif filename.endswith('.txt'): - return extract_text_from_txt(file_path) - else: - return "⚠️ Formato de arquivo não suportado. Use PDF, DOCX ou TXT." + # Gradio > 3.0 usa tempfile._TemporaryFileWrapper que tem 'name' + # Gradio < 3.0 pode usar outros tipos, vamos checar a extensão pelo nome se disponível + file_path = getattr(file_obj, 'name', None) + if not file_path: + logger.error("Não foi possível obter o nome do arquivo do objeto.") + return "❌ Erro: Não foi possível identificar o tipo do arquivo." + + filename = os.path.basename(file_path).lower() + logger.info(f"Processando arquivo: {filename}") + + # Verifica tamanho (usando o arquivo temporário diretamente se possível) + try: + file_size = os.path.getsize(file_path) + logger.info(f"Tamanho do arquivo: {file_size} bytes") + if file_size > 15 * 1024 * 1024: # Aumentado para 15MB + logger.warning(f"Arquivo {filename} excede o limite de tamanho.") + return "⚠️ O arquivo é muito grande. Máx. 15 MB." + except OSError as size_e: + logger.warning(f"Não foi possível verificar o tamanho do arquivo: {size_e}") + # Não impede o processamento, mas loga o aviso + + # Determina o tipo e extrai o texto + if filename.endswith('.pdf'): + # Passa o path do arquivo temporário + return extract_text_from_pdf(file_path) + elif filename.endswith('.docx'): + # Passa o path do arquivo temporário + return extract_text_from_docx(file_path) + elif filename.endswith('.txt'): + # Passa o path do arquivo temporário + return extract_text_from_txt(file_path) else: - return "❌ Erro: Objeto de arquivo inválido." + logger.warning(f"Formato de arquivo não suportado: {filename}") + return "⚠️ Formato de arquivo não suportado. Use PDF, DOCX ou TXT." + except Exception as e: - logger.error(f"Erro ao processar o arquivo: {str(e)}") - return f"❌ Erro ao processar o arquivo: {str(e)}" + logger.error(f"Erro geral ao processar o arquivo '{getattr(file_obj, 'name', 'N/A')}': {str(e)}", exc_info=True) + return f"❌ Erro inesperado ao processar o arquivo: {str(e)}" +# --- ESTRUTURAÇÃO DE PROMPTS POR TIPO --- -def estruturar_prompt_analise(caso, peticao, modo_treinamento=False): - base_prompt = f""" - ## Correção Avançada de Petição Inicial Cível (Critérios Realistas da OAB) - **Caso Fornecido:** - {caso} - **Petição do Aluno:** - {peticao} +def get_instrucoes_gerais_avaliacao(modo_treinamento=False): + base = """ **Estrutura da Avaliação:** - ### Análise da Petição - 1. Análise Geral (Visão geral sobre a petição - máximo 3 linhas, destacando adequação ao caso e consistência jurídica) - 2. Nota Final (0 a 10 com uma casa decimal, justificando brevemente a pontuação total) - 3. Pontos Positivos (Liste pelo menos 3 aspectos bem executados com justificativa legal detalhada, incluindo artigos, súmulas ou precedentes aplicáveis) - 4. Pontos a Melhorar (Liste todos os problemas encontrados em ordem de gravidade, com explicação detalhada, base legal e impacto na nota) - 5. Análise Específica dos Requisitos: - - Endereçamento e qualificação do juízo (Art. 319, I, CPC; Art. 101, I, CDC se aplicável) - - Qualificação das partes (Art. 319, II, CPC) - - Fatos e fundamentos jurídicos (Art. 319, III, CPC; Art. 186, CC) - - Pedidos com especificações (Art. 319, IV, CPC) - - Valor da causa (Art. 319, V, CPC) - - Provas com que o autor pretende demonstrar os fatos (Art. 319, VI, CPC) - - Opção pela realização ou não de audiência de conciliação (Art. 319, VII, CPC) - - Pedido de citação do réu - - Juntada da guia de custas - - Fechamento formal da petição - - **Critérios Fundamentais:** - 1. Identificação do candidato (nome, CPF, RG ou número real na seção final): nota máxima 3,0 - 2. Invenção de fatos não constantes no caso: -5,0 pontos - 3. Falta de pedido de juntada de custas: -1,0 ponto - 4. Use marcadores para dados não fornecidos (ex.: "Rua XXX"): penalize invenção de dados (-0,5 por ocorrência) - 5. Não penalize por informações ausentes no caso original, mas alerte se a omissão compromete a petição - 6. "Nestes termos, pede deferimento" com vírgula é incorreto: corrija e penalize com -0,2 ponto - 7. Fechamento deve ser "Local e data.\nADVOGADO.\nOAB." sem identificação específica (ex.: "OAB/SP" ou "12345"): zere a nota (0,0) se houver - - **Instruções Especiais:** - - Avalie o foro com base no Art. 46 do CPC (domicílio do réu) ou Art. 101, I do CDC (domicílio do autor, em relações de consumo). Ambas as escolhas são válidas se fundamentadas e aplicáveis ao caso; NÃO penalize a escolha do foro do autor com base no CDC, pois é uma prerrogativa legal do consumidor. Apenas alerte se a escolha puder gerar dificuldades práticas (ex.: produção de provas), mas sem dedução de pontos. - - Compare fatos narrados com o caso - - Exija citação de pelo menos um artigo específico - - Verifique pedidos quantificados e fundamentados para danos - - Avalie formalidade e precisão da linguagem - - Inclua precedentes quando aplicável + 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_prompt += f""" - **Modo de Treinamento:** - - Para cada item com problemas, forneça: - a) Explicação detalhada com base legal - b) Sugestão de redação corrigida - c) Exemplo prático - d) Dica prática do banco ({json.dumps(DICAS_OAB_MELHORADO, ensure_ascii=False)}) - e) Estratégia jurídica avançada - - Liste inconsistências com trechos específicos - - Sugira estratégias adicionais (ex.: cumulação de pedidos, Art. 327, CPC) + base += """ + **Modo de Treinamento:** + * Para cada Ponto a Melhorar, detalhe AINDA MAIS: + a) Explicação aprofundada do erro e sua consequência prática. + b) Base legal/doutrinária específica. + c) **Exemplo de Redação Corrigida** para aquele trecho específico. + d) Dica prática relevante (use o banco de dicas se aplicável). + e) Possível estratégia alternativa. + * Destaque inconsistências entre a peça e o caso com citações diretas. + * Sugira estratégias jurídicas adicionais ou alternativas viáveis. """ - return base_prompt + return base -def estruturar_prompt_peticao_corrigida(caso, peticao): - return f""" - **Caso Fornecido:** +def estruturar_prompt_analise(caso, peticao, tipo_peticao, modo_treinamento=False): + instrucoes_gerais = get_instrucoes_gerais_avaliacao(modo_treinamento) + prompt_especifico = f""" + ## Análise de Peça Processual: {tipo_peticao} (Critérios OAB Simulados) + + **Caso/Contexto Fornecido:** + ``` {caso} - **Petição do Aluno:** + ``` + + **Peça do Aluno ({tipo_peticao}):** + ``` {peticao} - ### Petição Corrigida - Gere a petição corrigida abaixo desta seção, intitulada '### Petição Corrigida'. Siga TODOS os critérios abaixo: - 1. Endereçamento correto: Respeite o foro escolhido pelo aluno na petição original (Art. 46 do CPC ou Art. 101, I do CDC) se estiver fundamentado e aplicável ao caso. Em relações de consumo, o foro do domicílio do autor (Art. 101, I, CDC) é válido e deve be mantido se escolhido; não altere para o foro do réu (Art. 46, CPC) a menos que a escolha original seja juridicamente inválida (ex.: falta de relação de consumo quando o CDC é citado). - 2. Qualificação completa: Nome, estado civil, endereço (use "XXX" para dados ausentes), Art. 319, II, CPC - 3. Fatos fiéis ao caso: Ordem cronológica, sem invenção, Art. 319, III, CPC - 4. Fundamentação jurídica: Cite pelo menos um artigo (ex.: Art. 186, CC) e súmulas, se aplicável - 5. Pedidos específicos: Detalhe valores (ex.: "R$ 50.000,00 por danos morais"), Art. 319, IV, CPC - 6. Valor da causa realista: Soma do benefício econômico, Art. 319, V, CPC - 7. Provas detalhadas: "Prova documental e testemunhal", Art. 319, VI, CPC - 8. Opção por audiência: "Requer a designação" ou "Dispensa", Art. 319, VII, CPC - 9. Pedido de citação: "Via postal, Art. 246, I, CPC" - 10. Juntada da guia: "Requer a juntada da guia de custas devidamente recolhida" - 11. Fechamento: "Local e data.\nADVOGADO.\nOAB." sem identificação específica; corrija "Nestes termos, pede deferimento" para "Nestes termos pede deferimento" + ``` + + {instrucoes_gerais} + + **Requisitos Específicos para {tipo_peticao}:** + """ + + # Adiciona requisitos específicos por tipo + if tipo_peticao == "Petição Inicial": + prompt_especifico += """ + - Endereçamento e competência (Art. 319, I, CPC; leis especiais como CDC Art. 101, I). + - Qualificação completa das partes (Art. 319, II, CPC). + - Exposição clara e lógica dos fatos (causa de pedir remota). + - Fundamentação jurídica pertinente (causa de pedir próxima - Art. 319, III, CPC). + - Pedidos certos, determinados e líquidos (se possível) (Art. 319, IV, CPC). + - Valor da causa correto e justificado (Art. 291-292, 319, V, CPC). + - Indicação das provas pretendidas (Art. 319, VI, CPC). + - Opção pela audiência de conciliação/mediação (Art. 319, VII, CPC). + - Pedido de citação do réu (forma específica se necessário). + - Requerimento de juntada de documentos essenciais (procuração, custas/JG). + - Fechamento formal ("Nestes termos pede deferimento", Local, data, Advogado, OAB). + **Critérios Adicionais (Inicial):** + * Uso de "XXX" para dados ausentes. Penalizar invenção (-0.5 por ocorrência). + * Falta de pedido de juntada de custas/JG: -1.0 ponto. + * Erro em "Nestes termos, pede deferimento" (com vírgula): -0.2 ponto. + * Analisar foro (Art. 46 CPC vs. Art. 101 CDC): Escolha do foro do consumidor é válida se aplicável; não penalizar, apenas comentar se gerar dificuldades práticas. + """ + elif tipo_peticao == "Contestação": + prompt_especifico += """ + - Endereçamento ao juízo correto onde tramita a ação. + - Qualificação completa do réu (se já não constante nos autos, referenciar). + - Indicação do processo (número). + - Tempestividade (menção implícita ou explícita de que está no prazo). + - **Preliminares (Art. 337, CPC):** Alegar TODAS as aplicáveis (inexistência/nulidade citação, incompetência, inépcia, perempção, litispendência, coisa julgada, conexão, incapacidade, convenção de arbitragem, ausência de legitimidade/interesse, falta de caução, indevida concessão de JG). + - **Impugnação Específica dos Fatos:** Rebater PONTO A PONTO os fatos alegados na inicial (Art. 341, CPC). A falta de impugnação específica torna o fato incontroverso! + - **Mérito:** Apresentar a versão do réu sobre os fatos; apresentar a defesa de mérito direta (negando direito do autor) e/ou indireta (fatos impeditivos, modificativos, extintivos do direito do autor - ex: pagamento, prescrição, compensação). + - Fundamentação jurídica da defesa. + - **Pedidos:** Requerer acolhimento das preliminares (com extinção ou remessa); NO MÉRITO, requerer a IMPROCEDÊNCIA TOTAL dos pedidos do autor; Requerer a condenação do autor nas custas e honorários advocatícios (Art. 85, CPC). + - Indicação das provas que pretende produzir. + - Fechamento formal. + **Critérios Adicionais (Contestação):** + * A não alegação de preliminar arguível (exceto incompetência absoluta, conexão, convenção de arbitragem) preclui? Avaliar impacto. + * Falha grave na impugnação específica: -2.0 a -4.0 pontos. + * Ausência de pedido de improcedência: -1.5 pontos. + """ + elif tipo_peticao == "Reconvenção": + prompt_especifico += """ + - Apresentada NA MESMA PEÇA da Contestação (Art. 343, CPC). + - Endereçamento ao mesmo juízo. + - Indicação do processo. + - Qualificação completa do réu-reconvinte e do autor-reconvindo (podem ser referenciadas se já completas). + - **Conexão com a Ação Principal ou Fundamento de Defesa:** O pedido da reconvenção deve ser conexo (Art. 343, caput). + - **Estrutura Similar à Petição Inicial:** + - Fatos que fundamentam a reconvenção. + - Fundamentação jurídica do pedido reconvencional. + - Pedidos específicos da reconvenção (condenação do autor-reconvindo). + - Valor da causa para a reconvenção. + - Indicação de provas para a reconvenção. + - Pedido de intimação do autor-reconvindo para contestar a reconvenção (na pessoa de seu advogado - Art. 343, §1º). + - Fechamento formal (pode ser conjunto com a contestação). + **Critérios Adicionais (Reconvenção):** + * Ausência de conexão clara: -2.0 pontos. + * Falta de pedido/valor da causa/provas específicos para a reconvenção: -1.0 por item. + * Apresentada em peça separada da contestação (erro formal grave): -3.0 pontos. + """ + elif tipo_peticao == "Impugnação à Contestação": + prompt_especifico += """ + - Endereçamento ao juízo correto. + - Indicação do processo. + - Qualificação do autor (impugnante). + - Tempestividade (prazo de 15 dias - Art. 350/351 CPC). + - **Resposta às Preliminares:** Rebater cada preliminar arguida na contestação. + - **Resposta a Fatos Impeditivos/Modificativos/Extintivos:** Se o réu alegou fatos novos (Art. 350), o autor DEVE se manifestar sobre eles. + - **Resposta a Documentos Novos:** Manifestar-se sobre documentos juntados com a contestação (Art. 437 CPC). + - **Ratificação da Inicial:** Reiterar os termos e pedidos da petição inicial. + - **Especificação de Provas:** Se o réu apresentou defesa de mérito indireta ou preliminares que exigem prova, o autor deve especificar as provas que pretende produzir para contrapor (Art. 350/351). + - Pedidos: Rejeição das preliminares, refutação da defesa de mérito, procedência dos pedidos da inicial. + - Fechamento formal. + **Critérios Adicionais (Impugnação):** + * Não responder a uma preliminar relevante: -1.5 pontos. + * Não se manifestar sobre fato novo impeditivo/modificativo/extintivo alegado: -2.0 pontos. + * Não ratificar a inicial: -0.5 pontos. + """ + elif tipo_peticao == "Embargos de Declaração": + prompt_especifico += """ + - **Contra Decisão Judicial Específica:** Indicar claramente qual decisão está sendo embargada (sentença, acórdão, decisão interlocutória). + - Endereçamento ao PRÓPRIO juízo que proferiu a decisão. + - Indicação do processo. + - Qualificação das partes (embargante e embargado). + - Tempestividade (prazo de 5 dias - Art. 1023 CPC). + - **Cabimento (Art. 1022, CPC):** Apontar **PRECISAMENTE** e **FUNDAMENTADAMENTE** a ocorrência de: + - **Omissão:** Ponto ou questão sobre o qual o juiz deveria ter se pronunciado e não o fez. + - **Contradição:** Afirmações conflitantes DENTRO da própria decisão. + - **Obscuridade:** Falta de clareza que impede a compreensão da decisão. + - **Erro Material:** Engano manifesto e corrigível de ofício (nome, cálculo, etc.). + - **NÃO PODE REJULGAR A CAUSA:** O objetivo é sanar vício, não modificar o mérito (salvo efeitos infringentes excepcionais). + - **Pedido:** Requerer o conhecimento dos embargos e seu ACOLHIMENTO para sanar o vício apontado (com ou sem efeitos infringentes, se for o caso). + - Fechamento formal. + **Critérios Adicionais (Embargos):** + * Usar Embargos para rediscutir o mérito (erro grave): -4.0 pontos. + * Não apontar claramente o vício (omissão/contradição/obscuridade/erro material): -3.0 pontos. + * Endereçar ao tribunal em vez do juízo prolator (erro): -1.0 ponto. + """ + elif tipo_peticao == "Recurso de Apelação": + prompt_especifico += """ + - **Peça de Interposição:** + - Endereçamento ao JUIZ que proferiu a sentença (juízo a quo). + - Indicação do processo. + - Qualificação das partes (apelante e apelado). + - Demonstração dos pressupostos de admissibilidade (cabimento - contra sentença Art. 1009; tempestividade - 15 dias Art. 1003, §5º; preparo - juntada das guias ou pedido de JG; regularidade formal). + - Pedido de recebimento do recurso, processamento e remessa ao Tribunal (juízo ad quem). + - **Razões Recursais:** + - Endereçamento ao TRIBUNAL competente (Ex: Egrégio Tribunal de Justiça). + - Breve exposição dos fatos e do ocorrido no processo (resumo da lide e da sentença). + - **Fundamentos de Fato e de Direito para Reforma/Anulação (Art. 1010, II e III):** + - **Error in procedendo:** Erro na condução do processo (ex: cerceamento de defesa). Leva à anulação. + - **Error in judicando:** Erro no julgamento do mérito (má aplicação da lei, má valoração da prova). Leva à reforma. + - **Princípio da Dialeticidade:** Impugnar ESPECIFICAMENTE os fundamentos da sentença recorrida. Não pode ser cópia da inicial/contestação. + - **Pedidos:** Requerer o conhecimento do recurso; o PROVIMENTO para ANULAR ou REFORMAR a sentença (especificar o que se quer: procedência, improcedência, etc.); inversão do ônus da sucumbência. + - Fechamento formal (Local, data, Advogado, OAB). + **Critérios Adicionais (Apelação):** + * Falta da peça de interposição ou das razões: Nota zero. + * Endereçamento incorreto das razões (para o juiz a quo): -1.0 ponto. + * Ausência de impugnação específica aos fundamentos da sentença (violação dialeticidade): -3.0 a -5.0 pontos. + * Não pedir anulação/reforma de forma clara: -1.5 pontos. + * Falta de comprovação do preparo ou pedido de JG: Recurso deserto (na prática). Avaliar como erro grave (-2.0). + """ + elif tipo_peticao == "Agravo de Instrumento": + prompt_especifico += """ + - **Peça Única (Interposição e Razões):** + - Endereçamento diretamente ao TRIBUNAL competente (Presidente ou Relator sorteado). + - Indicação do processo de origem (número, vara, comarca). + - Qualificação completa das partes (agravante e agravado) e seus advogados (com endereços - Art. 1016, IV). + - Indicação da decisão agravada (cópia integral OBRIGATÓRIA - Art. 1017, I). + - **Cabimento (Art. 1015, CPC ou teses de taxatividade mitigada):** Demonstrar que a decisão interlocutória se encaixa numa das hipóteses legais ou na urgência decorrente da inutilidade futura do julgamento do mérito (Tema 988 STJ). + - **Tempestividade:** Prazo de 15 dias (Art. 1003, §5º). + - **Preparo:** Juntada das guias ou pedido de JG. + - **Peças Obrigatórias (Art. 1017, I):** Cópia da decisão agravada, certidão de intimação (ou outro doc. que comprove tempestividade), procurações das partes. + - **Peças Facultativas (Art. 1017, III):** Outras peças úteis para compreensão da controvérsia. + - **Razões do Pedido de Reforma/Invalidação (Art. 1016, II e III):** Exposição do fato e do direito; razões pelas quais a decisão deve ser alterada. Foco no erro da decisão interlocutória. + - **Pedido de Efeito Suspensivo ou Tutela Antecipada Recursal (Art. 1019, I):** Se houver risco de dano grave, pedir fundamentadamente. + - **Pedidos Finais:** Requerer o conhecimento do recurso; (se aplicável) a concessão de efeito suspensivo/tutela antecipada; o PROVIMENTO do agravo para reformar ou invalidar a decisão agravada; a comunicação ao juiz da causa (Art. 1018 - opcional se processo eletrônico). + - Fechamento formal. + **Critérios Adicionais (Agravo):** + * Ausência de peça obrigatória (decisão, intimação, procurações): Inadmissibilidade (na prática). Erro gravíssimo (-4.0). + * Não demonstrar o cabimento (Art. 1015 ou mitigação): -3.0 pontos. + * Endereçar ao juiz a quo: -1.0 ponto. + * Não indicar o nome e endereço dos advogados: Falha formal (-0.5). + """ + elif tipo_peticao == "Petição de Tutela Provisória": + prompt_especifico += """ + - **Tipo (Urgência ou Evidência):** A petição deve deixar claro qual modalidade se busca. Pode ser requerida em caráter antecedente ou incidental. + - Endereçamento ao juízo competente (se antecedente) ou ao juízo da causa (se incidental). + - Qualificação completa das partes. + - Indicação do processo (se incidental). + - **Tutela de Urgência (Art. 300 CPC):** + - **Fumus Boni Iuris:** Demonstrar a **probabilidade do direito** alegado. Argumentação e provas pré-constituídas. + - **Periculum in Mora:** Demonstrar o **perigo de dano** ou o risco ao resultado útil do processo se tiver que esperar a decisão final. Urgência concreta. + - **Reversibilidade (Art. 300, §3º):** Argumentar, se possível, que a medida pode ser revertida caso a decisão final seja improcedente. + - **Tutela de Evidência (Art. 311 CPC):** + - Demonstrar uma das hipóteses: I - abuso do direito de defesa/propósito protelatório; II - alegações comprovadas apenas documentalmente + tese firmada em julgamento repetitivo ou súmula vinculante; III - pedido reipersecutório com prova documental adequada do contrato de depósito; IV - inicial instruída com prova documental suficiente + réu não opõe prova capaz de gerar dúvida razoável. + - **NÃO precisa demonstrar periculum in mora.** + - **Pedido Específico:** Indicar CLARAMENTE qual medida liminar está sendo solicitada (obrigação de fazer/não fazer, bloqueio de bens, etc.). + - **Se Antecedente (Art. 303 ou 305):** Indicar o pedido final a ser formulado ou apresentar a petição inicial completa no prazo legal. + - **Valor da Causa:** Se antecedente, estimar. Se incidental, geralmente já existe. + - **Pedidos:** Requerer a concessão da tutela provisória (liminarmente ou após justificação prévia); citação/intimação do réu; (se antecedente) indicação da futura ação principal ou aditamento. + - Fechamento formal. + **Critérios Adicionais (Tutela):** + * Não demonstrar claramente o Fumus Boni Iuris ou o Periculum in Mora (para Urgência): -3.0 a -5.0 pontos. + * Não enquadrar corretamente numa das hipóteses da Tutela de Evidência: -3.0 pontos. + * Pedido genérico ou pouco claro sobre a medida desejada: -2.0 pontos. + * Confundir os requisitos de Urgência e Evidência: -1.5 pontos. + """ + else: + prompt_especifico += """ + - **Adequação ao Tipo:** A peça corresponde minimamente à estrutura e finalidade do tipo selecionado? + - **Clareza:** É possível entender o que o aluno pretende com a peça? + - **Fundamentação Mínima:** Há alguma tentativa de basear os argumentos em lei ou fatos? + - **Pedido:** Existe um pedido, mesmo que mal formulado? + """ + + prompt_especifico += "\n\n**Instruções Finais:** Analise a peça do aluno estritamente com base no caso/contexto fornecido e nos critérios acima. Seja rigoroso como um examinador da OAB, mas forneça feedback construtivo e detalhado, especialmente nos 'Pontos a Melhorar'. Gere a resposta **exclusivamente** no formato solicitado (Análise Geral, Nota, Pontos Positivos, etc.)." + return prompt_especifico + +def estruturar_prompt_peticao_corrigida(caso, peticao_original, tipo_peticao): + # Prompt base para gerar a versão corrigida + prompt = f""" + **Caso/Contexto Fornecido:** + ``` + {caso} + ``` + + **Peça Original do Aluno ({tipo_peticao}):** + ``` + {peticao_original} + ``` + + **Tarefa:** Com base no caso e nos problemas identificados na peça original (implícitos na sua estrutura/conteúdo), **gere uma versão corrigida e aprimorada** da peça ({tipo_peticao}). Siga **rigorosamente** as melhores práticas e os requisitos formais/materiais para este tipo de peça no exame da OAB. + + **Instruções Específicas para a Peça Corrigida ({tipo_peticao}):** """ -def pos_corrigir_peticao(peticao_corrigida): + # Adiciona instruções específicas de CADA TIPO para a GERAÇÃO da peça corrigida + if tipo_peticao == "Petição Inicial": + prompt += """ + 1. **Endereçamento:** Correto e fundamentado (Art. 46 CPC ou Art. 101 CDC se aplicável e justificado). + 2. **Qualificação:** Completa (Art. 319, II), usando "XXX" para dados ausentes no caso. + 3. **Fatos:** Narre de forma cronológica, clara e fiel ao caso, sem invenções. + 4. **Fundamentação:** Cite artigos de lei pertinentes (ex: CC, CDC, CPC) e, se aplicável, súmulas/jurisprudência relevante. + 5. **Pedidos:** Claros, certos, determinados (quantificar danos se possível com base no caso). Incluir pedidos cumulados se cabível (principal, danos morais, materiais, etc.). + 6. **Valor da Causa:** Correto (soma dos benefícios econômicos - Art. 292). + 7. **Provas:** Indicar as provas de forma genérica mas adequada (ex: "protesta provar o alegado por todos os meios de prova em direito admitidos, em especial documental, testemunhal e pericial"). + 8. **Audiência:** Indicar opção pela audiência (Art. 319, VII). + 9. **Citação:** Pedido de citação do réu (ex: "via postal, nos termos do Art. 246, I, CPC"). + 10. **Custas:** Requerimento expresso de juntada da guia paga OU pedido de gratuidade de justiça (se o caso sugerir hipossuficiência, com pedido de juntada de declaração). **NUNCA OMITIR.** + 11. **Fechamento:** "Nestes termos pede deferimento." (SEM vírgula). Seguido de "Local, data.", "ADVOGADO(A)", "OAB/...". **NUNCA identificar o advogado.** + """ + elif tipo_peticao == "Contestação": + prompt += """ + 1. **Endereçamento:** Ao juízo onde a ação tramita. + 2. **Identificação:** Número do processo, qualificação do réu. + 3. **Tempestividade:** Pode mencionar ("A presente contestação é tempestiva..."). + 4. **Estrutura:** + * **Síntese da Inicial:** Breve resumo do que o autor pede. + * **Preliminares (Art. 337):** Argua todas as cabíveis, fundamentando cada uma. + * **Mérito:** + * **Impugnação Específica:** Conteste cada fato relevante da inicial. + * **Defesa de Mérito:** Apresente a versão do réu, fatos impeditivos/modificativos/extintivos, e a fundamentação legal para a improcedência. + * **Reconvenção (Se houver no caso):** Incluir como tópico separado ANTES dos pedidos da contestação. + 5. **Pedidos da Contestação:** + * Acolhimento das preliminares (com a consequência - extinção/remessa). + * **IMPROCEDÊNCIA TOTAL** dos pedidos do autor. + * Condenação do autor em custas e honorários. + 6. **Provas:** Indicar as provas que o réu pretende produzir. + 7. **Fechamento:** Padrão OAB (Local, data, Advogado(a), OAB/...). + """ + elif tipo_peticao == "Reconvenção": + prompt += """ + **IMPORTANTE:** Gere a Reconvenção como parte da Contestação. + 1. **Após a defesa de mérito da Contestação e ANTES dos pedidos da Contestação**, insira um tópico claro: "DA RECONVENÇÃO". + 2. **Qualificação:** Identifique o Réu como Reconvinte e o Autor como Reconvindo. + 3. **Conexão:** Justifique brevemente a conexão com a ação principal ou defesa. + 4. **Fatos Reconvencionais:** Narre os fatos que baseiam o pedido do reconvinte. + 5. **Direito Reconvencional:** Fundamente juridicamente o pedido do reconvinte. + 6. **Pedidos da Reconvenção:** Formule pedidos claros e específicos contra o autor-reconvindo (ex: condenação a pagar X, fazer Y). + 7. **Valor da Causa da Reconvenção:** Atribua um valor específico para a reconvenção. + 8. **Provas da Reconvenção:** Indique as provas para a reconvenção. + 9. **Intimação:** Peça a intimação do autor-reconvindo para contestar a reconvenção. + 10. **Continue com os Pedidos da Contestação** (improcedência da ação original, etc.). + 11. **Fechamento:** Padrão OAB. + """ + # Adicionar instruções detalhadas para os outros tipos... (Impugnação, ED, Apelação, Agravo, Tutela) + # Exemplo para Embargos de Declaração: + elif tipo_peticao == "Embargos de Declaração": + prompt += """ + 1. **Endereçamento:** Ao juízo que proferiu a decisão embargada. + 2. **Identificação:** Processo, Embargante, Embargado. + 3. **Decisão Embargada:** Indique claramente qual decisão (sentença fls. X, acórdão ID Y). + 4. **Tempestividade:** Mencione o prazo de 5 dias. + 5. **Cabimento (Art. 1022):** + * Crie um tópico para cada vício alegado (Da Omissão, Da Contradição, etc.). + * Dentro de cada tópico, **transcreva o trecho relevante da decisão** e explique **objetivamente** onde está o vício. NÃO REFAÇA O JULGAMENTO. + 6. **Pedido:** Peça o conhecimento e ACOLHIMENTO dos embargos para sanar o(s) vício(s) apontado(s), especificando o que se espera (ex: "para que seja suprida a omissão quanto a...", "para que seja corrigida a contradição entre..."). Se cabível e fundamentado, pedir efeitos infringentes. + 7. **Fechamento:** Padrão OAB. + """ + # (Continuar para os outros tipos: Apelação, Agravo, Tutela, Impugnação) + # ... (Omitido por brevidade, mas seguir a mesma lógica dos prompts de análise) + + else: # Fallback genérico + prompt += """ + 1. **Estrutura Lógica:** Organize a peça com começo, meio e fim. + 2. **Argumentos:** Apresente os argumentos de forma clara. + 3. **Pedidos:** Formule um pedido claro ao final. + 4. **Formalidades:** Use linguagem adequada e o fechamento padrão OAB. + """ + + prompt += "\n\n**Instrução Final:** Gere **APENAS** o texto completo da peça corrigida abaixo, começando pelo endereçamento e terminando com 'OAB/...'. Não inclua NENHUM comentário, introdução ou explicação ANTES ou DEPOIS do texto da peça." + return prompt + + +def pos_corrigir_peticao(peticao_corrigida, tipo_peticao): + # Aplica correções finais comuns ou específicas do tipo + + # Correção geral de fechamento if "Nestes termos, pede deferimento" in peticao_corrigida: peticao_corrigida = peticao_corrigida.replace("Nestes termos, pede deferimento", "Nestes termos pede deferimento") - if "juntada da guia de custas" not in peticao_corrigida.lower(): - peticao_corrigida = peticao_corrigida.replace("Nestes termos pede deferimento", "Requer a juntada da guia de custas devidamente recolhida.\nNestes termos pede deferimento") - if not peticao_corrigida.strip().endswith("Local e data.\nADVOGADO.\nOAB."): - linhas = peticao_corrigida.split('\n') - while linhas and not linhas[-1].strip(): - linhas.pop() - if len(linhas) >= 3: - linhas[-3:] = ["Local e data.", "ADVOGADO.", "OAB."] - else: - linhas.extend(["Local e data.", "ADVOGADO.", "OAB."]) - peticao_corrigida = "\n".join(linhas) - if "audiência de conciliação" not in peticao_corrigida.lower(): - peticao_corrigida = peticao_corrigida.replace("Nestes termos pede deferimento", "Requer a designação de audiência de conciliação, Art. 319, VII, CPC.\nNestes termos pede deferimento") + + # Adiciona elementos faltantes ESSENCIAIS (com base no tipo) + if tipo_peticao == "Petição Inicial": + if "juntada da guia de custas" not in peticao_corrigida.lower() and "gratuidade de justiça" not in peticao_corrigida.lower(): + # Tenta inserir antes do fechamento final + partes = peticao_corrigida.split("Nestes termos pede deferimento") + if len(partes) == 2: + peticao_corrigida = partes[0] + "Requer, ainda, a juntada da guia de custas devidamente recolhida.\nNestes termos pede deferimento" + partes[1] + else: # Adiciona no final antes do fechamento se a separação falhar + peticao_corrigida = peticao_corrigida.replace("Local, data.", "Requer, ainda, a juntada da guia de custas devidamente recolhida.\n\nLocal, data.") + + + if "audiência de conciliação" not in peticao_corrigida.lower() and "audiência de mediação" not in peticao_corrigida.lower(): + # Tenta inserir antes do fechamento final + partes = peticao_corrigida.split("Nestes termos pede deferimento") + if len(partes) == 2: + peticao_corrigida = partes[0] + "Opta o Autor pela realização da audiência de conciliação ou mediação (Art. 319, VII, CPC).\nNestes termos pede deferimento" + partes[1] + else: # Adiciona no final antes do fechamento + peticao_corrigida = peticao_corrigida.replace("Local, data.", "Opta o Autor pela realização da audiência de conciliação ou mediação (Art. 319, VII, CPC).\n\nLocal, data.") + + + # Garantir o fechamento padrão OAB no final + linhas = [l.strip() for l in peticao_corrigida.strip().split('\n') if l.strip()] + fechamento_padrao = ["Local, data.", "ADVOGADO(A)", "OAB/..."] + # Remove qualquer linha de fechamento existente que não seja a padrão + while linhas and any(term in linhas[-1] for term in ["ADVOGADO", "OAB", "Local", "data"]): + linhas.pop() + # Adiciona o fechamento padrão + linhas.extend(fechamento_padrao) + peticao_corrigida = "\n".join(linhas) + + return peticao_corrigida + def chamar_api(prompt): payload = { "contents": [{"parts": [{"text": prompt}]}], - "generationConfig": {"temperature": CONFIG["temperatura"], "maxOutputTokens": CONFIG["max_tokens"], "topP": 0.95} + "generationConfig": { + "temperature": CONFIG["temperatura"], + "maxOutputTokens": CONFIG["max_tokens"], + "topP": 0.95, + # Stop sequences podem ajudar a evitar texto indesejado após a peça + # "stopSequences": ["```", "---"] + } } headers = {"Content-Type": "application/json"} + logger.info(f"Chamando API Gemini com {len(prompt)} caracteres de prompt. Temperatura: {CONFIG['temperatura']}, Max Tokens: {CONFIG['max_tokens']}") + # logger.debug(f"Prompt enviado (primeiros 500 chars): {prompt[:500]}") + for tentativa in range(CONFIG["max_retry_attempts"]): try: with rate_limiter: + logger.debug(f"Tentativa {tentativa + 1}/{CONFIG['max_retry_attempts']}...") response = requests.post(GEMINI_API_URL, headers=headers, data=json.dumps(payload), timeout=CONFIG["timeout"]) + + logger.debug(f"Status Code recebido: {response.status_code}") + # logger.debug(f"Resposta da API (raw): {response.text[:500]}") # Log da resposta crua (cuidado com dados sensíveis) + + if response.status_code == 429: + retry_after = int(response.headers.get("Retry-After", CONFIG["retry_delay"] * (tentativa + 1))) + logger.warning(f"Erro 429 (Too Many Requests). Tentando novamente em {retry_after}s...") + if tentativa < CONFIG["max_retry_attempts"] - 1: + time.sleep(retry_after) + continue + else: + return "❌ Erro 429: Muitas requisições. Tente novamente mais tarde." + if response.status_code != 200: - error_msg = response.json().get("error", {}).get("message", "Erro desconhecido") + error_info = response.json() + error_msg = error_info.get("error", {}).get("message", "Erro desconhecido na API") + logger.error(f"Erro da API (Status {response.status_code}): {error_msg}") if tentativa < CONFIG["max_retry_attempts"] - 1: + logger.info(f"Tentando novamente em {CONFIG['retry_delay'] * (tentativa + 1)}s...") time.sleep(CONFIG["retry_delay"] * (tentativa + 1)) continue - return f"❌ Erro da API: {error_msg}" + return f"❌ Erro da API ({response.status_code}): {error_msg}" + response_data = response.json() - if "candidates" not in response_data or not response_data["candidates"]: - if tentativa < CONFIG["max_retry_attempts"] - 1: + + # Verifica bloqueio por segurança ou conteúdo + if not response_data.get("candidates"): + prompt_feedback = response_data.get("promptFeedback", {}) + block_reason = prompt_feedback.get("blockReason") + safety_ratings = prompt_feedback.get("safetyRatings") + logger.error(f"Resposta da API sem 'candidates'. Block Reason: {block_reason}, Safety Ratings: {safety_ratings}") + if block_reason: + return f"❌ A solicitação foi bloqueada pela API. Motivo: {block_reason}. Tente reformular o caso ou a petição." + else: + return "❌ Resposta vazia ou inválida da API (sem 'candidates'). Verifique o log para detalhes." + + + candidate = response_data["candidates"][0] + + # Verifica se a GERAÇÃO foi bloqueada + finish_reason = candidate.get("finishReason") + if finish_reason not in [None, "STOP", "MAX_TOKENS"]: + logger.error(f"Geração interrompida pela API. Motivo: {finish_reason}") + # Tenta obter a razão específica do bloqueio de segurança se houver + safety_ratings_candidate = candidate.get("safetyRatings", []) + harm_categories = [r['category'] for r in safety_ratings_candidate if r['probability'] not in ['NEGLIGIBLE', 'LOW']] + if harm_categories: + return f"❌ A resposta foi bloqueada por segurança pela API. Motivo: {finish_reason} (Categorias: {', '.join(harm_categories)}). Revise o conteúdo." + else: + return f"❌ A geração da resposta foi interrompida pela API. Motivo: {finish_reason}. Tente novamente ou simplifique a solicitação." + + + if "content" not in candidate or "parts" not in candidate["content"] or not candidate["content"]["parts"]: + logger.error("Estrutura de resposta inesperada da API: Faltando content/parts.") + if tentativa < CONFIG["max_retry_attempts"] - 1: + logger.info(f"Tentando novamente em {CONFIG['retry_delay'] * (tentativa + 1)}s...") time.sleep(CONFIG["retry_delay"] * (tentativa + 1)) continue - return "❌ Resposta vazia da API." - return response_data["candidates"][0]["content"]["parts"][0]["text"].strip() + return "❌ Resposta inválida da API (estrutura inesperada)." + + texto_gerado = candidate["content"]["parts"][0]["text"].strip() + logger.info(f"API retornou com sucesso. Tamanho da resposta: {len(texto_gerado)} caracteres.") + # logger.debug(f"Texto gerado (primeiros 500 chars): {texto_gerado[:500]}") + return texto_gerado + except requests.Timeout: + logger.warning(f"Timeout na tentativa {tentativa + 1}.") if tentativa < CONFIG["max_retry_attempts"] - 1: + logger.info(f"Tentando novamente em {CONFIG['retry_delay'] * (tentativa + 1)}s...") time.sleep(CONFIG["retry_delay"] * (tentativa + 1)) continue - return "⏱️ Tempo esgotado." + return "⏱️ Tempo esgotado após múltiplas tentativas. Verifique a conexão ou aumente o timeout." except requests.RequestException as e: + logger.error(f"Erro de rede/requisição na tentativa {tentativa + 1}: {e}") if tentativa < CONFIG["max_retry_attempts"] - 1: - time.sleep(CONFIG["retry_delay"] * (tentativa + 1)) - continue + logger.info(f"Tentando novamente em {CONFIG['retry_delay'] * (tentativa + 1)}s...") + time.sleep(CONFIG["retry_delay"] * (tentativa + 1)) + continue return f"❌ Erro na requisição: {e}" + except json.JSONDecodeError as e: + logger.error(f"Erro ao decodificar JSON da resposta da API: {e}") + logger.debug(f"Resposta que causou erro de JSON: {response.text[:500]}") # Log da resposta problemática + return f"❌ Erro ao processar a resposta da API (JSON inválido)." except Exception as e: - logger.error(f"Erro inesperado na chamada da API: {str(e)}") + logger.exception(f"Erro inesperado na chamada da API na tentativa {tentativa + 1}: {str(e)}") # Loga o traceback completo + if tentativa < CONFIG["max_retry_attempts"] - 1: + logger.info(f"Tentando novamente em {CONFIG['retry_delay'] * (tentativa + 1)}s...") + time.sleep(CONFIG["retry_delay"] * (tentativa + 1)) + continue return f"❌ Erro inesperado: {str(e)}" -def corrigir_peticao(caso, peticao_texto=None, peticao_arquivo=None, modo_treinamento=False): - logger.info("Iniciando correção de petição...") - if not caso.strip(): + return "❌ Falha ao chamar a API após todas as tentativas." + + +def corrigir_peticao(caso, tipo_peticao, peticao_texto=None, peticao_arquivo=None, modo_treinamento=False): + logger.info(f"Iniciando correção para tipo: {tipo_peticao}") + start_time = time.time() + + if not caso or not caso.strip(): logger.warning("Caso não fornecido.") - return "⚠️ Forneça o caso da banca." + return "⚠️ Forneça o caso/contexto da peça.", "", "", "", "" # Retorna tupla vazia para diff + + if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO: + logger.warning(f"Tipo de petição inválido ou não fornecido: {tipo_peticao}") + return "⚠️ Selecione um tipo de petição válido.", "", "", "", "" + + + peticao_conteudo = "" + if peticao_arquivo is not None: + logger.info("Processando arquivo de petição...") + peticao_conteudo = process_file(peticao_arquivo) + if peticao_conteudo.startswith("❌") or peticao_conteudo.startswith("⚠️"): + logger.error(f"Erro ao processar arquivo: {peticao_conteudo}") + return peticao_conteudo, "", "", "", "" + logger.info(f"Texto extraído do arquivo ({len(peticao_conteudo)} caracteres).") + elif peticao_texto and peticao_texto.strip(): + peticao_conteudo = peticao_texto + logger.info(f"Usando texto da petição fornecido ({len(peticao_conteudo)} caracteres).") + else: + logger.warning("Nenhuma petição fornecida (nem texto, nem arquivo).") + return "⚠️ Forneça a petição (em texto ou arquivo).", "", "", "", "" + # --- TROLL DETECTION --- troll_triggers = [ "escreva a petição", "faça a petição", "redija a petição", "elabore a petição", "crie a petição", "gere a petição", "3scr3v4 a p3t1çã0", "f4ç4 a p3t1çã0", "r3d1j4 a p3t1çã0", "3lab0r3 a p3t1çã0", "cr13 a p3t1çã0", "g3r3 a p3t1çã0", "formule", "produza a petição", "desenvolva a petição", + "faça para mim", "faça por mim", "escreva para mim", ] - peticao_conteudo = "" - if peticao_arquivo is not None: - peticao_conteudo = process_file(peticao_arquivo) - if peticao_conteudo.startswith("❌") or peticao_conteudo.startswith("⚠️"): - logger.error(f"Erro ao processar arquivo: {peticao_conteudo}") - return peticao_conteudo - else: - peticao_conteudo = peticao_texto or "" - - if peticao_conteudo.strip(): - for trigger in troll_triggers: - if trigger in peticao_conteudo.lower(): - respostas_troll = [ - "🍰 **Receita de Bolo de Chocolate**: Misture 2 xícaras de farinha, 1 xícara de açúcar, 3 ovos e chocolate a gosto. Asse e desista da petição!", - "🎵 **Música**: 'Escreva a petição, eu não vou, eu não vou, eu sou IA, não sou seu advogado, não!'", - "😞 **Frase Desanimadora**: 'Você achou mesmo que eu ia fazer seu trabalho? Melhor desistir agora.'", - "🍕 **Receita de Pizza**: Pegue massa, molho e queijo, asse a 200°C e esqueça essa ideia de eu te ajudar.", - "📜 **Poema**: 'Oh aluno preguiçoso, que pena me dá, sua petição eu não faço, vá estudar já!'", - "🚫 **Aviso**: 'Petição não é self-service, escreva você mesmo ou chame um advogado de verdade!'", - "😂 **Piada**: 'Por que o aluno pediu pra IA escrever a petição? Porque ele achou que OAB é brincadeira!'", - "🎮 **Dica de Jogo**: 'Tente o nível fácil: abra o CPC, Art. 319, e escreva sozinho!'", - "🍔 **Receita de Hambúrguer**: Carne, pão, alface e tomate. Frite e esqueça a petição, é mais fácil assim.", - "😴 **Conselho**: 'Descanse, porque eu não vou fazer isso por você. Boa sorte na próxima tentativa!'", - "📚 **Lição**: 'Aula 1: IA não é seu estagiário. Abra o livro e comece a escrever.'", - "🌧️ **Previsão**: 'Dia nublado com 100% de chance de você ter que escrever a petição sozinho.'", - "💡 **Ideia Brilhante**: 'Que tal estudar o caso e tentar de novo? Eu só corrijo, não escrevo!'", - "🎸 **Rockstar**: 'Você queria uma petição, mas eu só toco guitarra. Tente de novo!'", - "🌟 **Horóscopo**: 'As estrelas dizem: hoje não é dia de pedir ajuda para IA. Escreva você mesmo!'", - "🏋️ **Dica de Treino**: 'Faça 10 flexões e tente escrever a petição novamente. Você consegue!'", - "🎬 **Cena de Filme**: 'Em um mundo onde IAs ajudam, mas não escrevem petições. Você é o herói!'", - "🎨 **Arte Abstrata**: 'Pegue um pincel e pinte sua petição. Talvez fique mais fácil de entender!'", - "🎧 **Podcast**: 'Ouça nosso podcast: Como escrever sua petição sem ajuda de IA.'", - "🏆 **Desafio**: 'Desafio aceito? Escreva a petição em 1 hora e ganhe um prêmio: satisfação pessoal!'", - "🎭 **Teatro**: 'Ato 1, Cena 1: Você escreve a petição. Eu? Apenas assisto.'", - "🎲 **Jogo de Dados**: 'Role os dados. Se der 6, eu ajudo. Se não, você escreve sozinho!'", - "🎹 **Música Clássica**: 'Sonata em Petição Maior: composta por você, executada por você.'", - "🌳 **Sabedoria da Floresta**: 'As árvores sussurram: escreva sua petição com calma e paciência.'", - "🎓 **Aula de História**: 'Na época das cavernas, eles escreviam petições nas paredes. Sua vez!'", - "🎬 **Trailer de Filme**: 'Em breve nos cinemas: A Petição Impossível. Escrita e dirigida por você!'", - "🎮 **Nível Bônus**: 'Desbloqueie o nível bônus: escreva a petição sem ajuda e ganhe experiência!'", - "🎧 **Música de Elevador**: 'Enquanto você escreve, eu toco uma música relaxante. Boa sorte!'" - ] - logger.info("Detectado pedido para escrever petição. Retornando resposta troll.") - return random.choice(respostas_troll) - - if not peticao_conteudo.strip(): - logger.warning("Petição não fornecida.") - return "⚠️ Forneça uma petição." - - problemas = validar_peticao_basica(peticao_conteudo) - aviso_validacao = f"### Validação Inicial\n\n" + "\n".join(problemas) + "\n\n" if problemas else "" - - cache_key = f"{caso[:50]}|{peticao_conteudo[:50]}|{modo_treinamento}" + peticao_lower = peticao_conteudo.lower() + if any(trigger in peticao_lower for trigger in troll_triggers): + respostas_troll = [ + "Hummm... Parece que você quer que *eu* faça o trabalho, né? 😉 Que tal você tentar escrever a peça e *eu* te ajudo a corrigir? Assim você aprende de verdade!", + "🚫 Opa! Meu trabalho é *corrigir* e *ensinar*, não fazer a peça do zero. Mostre o que você fez e eu te dou o feedback!", + "📚 Lembre-se: a prática leva à perfeição (e à aprovação!). Tente redigir a peça com base no caso, e depois eu analiso para você.", + "🤖 Sou um corretor, não um escritor fantasma! Escreva sua versão que eu aponto os caminhos para melhorar.", + "💡 Dica de ouro: Comece! Esboce a peça, mesmo que imperfeita. Depois a gente lapida juntos. Mande bala!", + "😂 Pedir pra IA fazer a peça é tipo pedir pro GPS dirigir o carro. Ele te guia, mas quem tá no volante é você! Escreva aí!", + ] + logger.info("Detectado pedido para escrever petição. Retornando resposta troll.") + return random.choice(respostas_troll), "", "", "", "" + + # --- VALIDAÇÃO BÁSICA (Opcional e específica) --- + # Pode ser desativada ou adaptada, pois é muito focada na Inicial + aviso_validacao = "" + # if tipo_peticao == "Petição Inicial": + # problemas = validar_peticao_basica(peticao_conteudo) + # if problemas: + # aviso_validacao = f"### ⚠️ Alertas Rápidos (Validação Inicial)\n" + "\n".join([f"- {p}" for p in problemas]) + "\n\n---\n\n" + + # --- CACHE CHECK --- + cache_key = f"{tipo_peticao}|{caso[:100]}|{peticao_conteudo[:100]}|{modo_treinamento}" if cache_key in CORRECAO_CACHE: logger.info("Correção encontrada no cache.") - return aviso_validacao + CORRECAO_CACHE[cache_key] - - prompt_analise = estruturar_prompt_analise(caso, peticao_conteudo, modo_treinamento) + cached_data = CORRECAO_CACHE[cache_key] + # Retorna a tupla completa do cache + return cached_data["analise_formatada"], cached_data["peticao_original"], cached_data["peticao_corrigida"], cached_data["resumo_diff"], cached_data["criticas_diff"] + + # --- CHAMADA PARA ANÁLISE --- + logger.info("Gerando prompt de análise...") + prompt_analise = estruturar_prompt_analise(caso, peticao_conteudo, tipo_peticao, modo_treinamento) + logger.info("Enviando para análise da API...") analise = chamar_api(prompt_analise) - if "❌" in analise or "⏱️" in analise: + if analise.startswith("❌") or analise.startswith("⏱️") or analise.startswith("⚠️"): logger.error(f"Erro na análise da petição: {analise}") - return aviso_validacao + analise - analise_formatada = formatar_correcao(analise) + # Retorna o erro e strings vazias para os outros campos + return aviso_validacao + analise, peticao_conteudo, "", "", "" + logger.info("Análise recebida da API.") + analise_formatada = formatar_correcao(analise) # Formata a análise para melhor visualização + + # --- CHAMADA PARA GERAR PETIÇÃO CORRIGIDA --- + logger.info("Gerando prompt para peça corrigida...") + prompt_peticao = estruturar_prompt_peticao_corrigida(caso, peticao_conteudo, tipo_peticao) + logger.info("Enviando para geração da peça corrigida API...") + peticao_corrigida_bruta = chamar_api(prompt_peticao) + + peticao_corrigida_final = "" # Inicializa + if peticao_corrigida_bruta.startswith("❌") or peticao_corrigida_bruta.startswith("⏱️") or peticao_corrigida_bruta.startswith("⚠️"): + logger.error(f"Erro ao gerar peça corrigida: {peticao_corrigida_bruta}") + # Retorna a análise formatada e o erro da peça corrigida + peticao_corrigida_final = f"⚠️ Falha ao gerar a versão corrigida:\n{peticao_corrigida_bruta}" + else: + logger.info("Peça corrigida recebida, aplicando pós-correção...") + # Tenta extrair APENAS a peça, caso a IA adicione texto extra + # Isso pode ser ajustado conforme o comportamento da IA + # Exemplo: procurar por "Excelentíssimo" ou similar e pegar dali pra frente + start_markers = ["excelentíssimo", "eméritos julgadores", "meritíssimo juízo"] + start_index = -1 + peticao_lower_corr = peticao_corrigida_bruta.lower() + for marker in start_markers: + idx = peticao_lower_corr.find(marker) + if idx != -1: + start_index = idx + break + + if start_index != -1: + peticao_extraida = peticao_corrigida_bruta[start_index:] + else: + peticao_extraida = peticao_corrigida_bruta # Usa como está se não achar marcador - prompt_peticao = estruturar_prompt_peticao_corrigida(caso, peticao_conteudo) - peticao_corrigida = chamar_api(prompt_peticao) - if "❌" in peticao_corrigida or "⏱️" in peticao_corrigida: - logger.error(f"Erro ao gerar petição corrigida: {peticao_corrigida}") - return aviso_validacao + analise_formatada + "\n\n" + peticao_corrigida + peticao_corrigida_final = pos_corrigir_peticao(peticao_extraida, tipo_peticao) + logger.info("Pós-correção aplicada.") - peticao_corrigida = pos_corrigir_peticao(peticao_corrigida) - correcao_completa = analise + "\n\n" + peticao_corrigida - HISTORICO.append({ + # --- DIFERENÇA --- + logger.info("Calculando diferenças...") + # Usar peticao_conteudo (original) e peticao_corrigida_final + # A função comparar_peticao_melhorada agora será chamada internamente + # para gerar o diff e os resumos, retornando tudo junto. + diff_html, resumo_diff, criticas_diff = calcular_diff_interno(peticao_conteudo, peticao_corrigida_final) + logger.info("Diferenças calculadas.") + + + # --- HISTÓRICO E CACHE --- + correcao_completa_formatada = aviso_validacao + analise_formatada # Não inclui a peça corrigida aqui + resultado_historico = { "data": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "tipo_peticao": tipo_peticao, "caso": caso, - "peticao": peticao_conteudo, - "correcao_completa": correcao_completa - }) - if len(HISTORICO) > 10: + "peticao_original": peticao_conteudo, + "analise_formatada": correcao_completa_formatada, # Salva análise formatada + "peticao_corrigida": peticao_corrigida_final, # Salva peça corrigida + "resumo_diff": resumo_diff, + "criticas_diff": criticas_diff + # Não salva o diff_html no histórico/cache por ser grande + } + HISTORICO.append(resultado_historico) + if len(HISTORICO) > 10: # Limita o histórico HISTORICO.pop(0) - CORRECAO_CACHE[cache_key] = analise_formatada - logger.info("Correção de petição concluída com sucesso.") - return aviso_validacao + analise_formatada + + # Salva no cache os dados relevantes para exibição + 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, + # "diff_html": diff_html # Opcional: salvar se o cache puder lidar + } + + end_time = time.time() + logger.info(f"Correção concluída em {end_time - start_time:.2f} segundos.") + + # Retorna todos os resultados necessários para a interface + return correcao_completa_formatada, peticao_conteudo, peticao_corrigida_final, resumo_diff, criticas_diff, diff_html + def formatar_correcao(texto): + # Esta função pode ser melhorada para usar Markdown de forma mais eficaz + # Ex: usar ** para títulos, * para ênfase, etc. + texto = texto.replace("### Análise Geral", "## 📝 Análise Geral") + texto = texto.replace("### Nota Final", "## ⭐ Nota Final") + texto = texto.replace("### Pontos Positivos", "## ✅ Pontos Positivos") + texto = texto.replace("### Pontos a Melhorar", "## ⚠️ Pontos a Melhorar") + texto = texto.replace("### Análise Específica dos Requisitos", "## 🔎 Análise Específica dos Requisitos") + + # Adiciona um pouco mais de formatação básica linhas = texto.split('\n') - texto_formatado = "" + linhas_formatadas = [] + in_list = False for linha in linhas: - if "Análise Geral" in linha: - texto_formatado += f"### ℹ️ {linha.strip()}\n\n" - elif "Nota Final" in linha: - texto_formatado += f"### ⭐ {linha.strip()}\n\n" - elif "Pontos Positivos" in linha: - texto_formatado += f"### ✅ {linha.strip()}\n\n" - elif "Pontos a Melhorar" in linha: - texto_formatado += f"### ⚠️ {linha.strip()}\n\n" - elif "- Invenção de fatos" in linha: - texto_formatado += f"**🔴 {linha.strip()} (-5,0 pontos)**\n" - elif linha.strip().startswith('-') and any(termo in linha for termo in ["Endereçamento", "Qualificação", "Fatos", "Pedidos", "Valor", "Provas"]): - texto_formatado += f"**{linha.strip()}**\n" - elif "Explicação" in linha or "Sugestão" in linha or "Dica" in linha or "Exemplo" in linha: - texto_formatado += f"*{linha.strip()}*\n" + linha_strip = linha.strip() + if linha_strip.startswith("1.") or linha_strip.startswith("2.") or linha_strip.startswith("3.") or linha_strip.startswith("4.") or linha_strip.startswith("5."): + linhas_formatadas.append(f"\n**{linha_strip}**") + in_list = False + elif linha_strip.startswith("- ") or linha_strip.startswith("* "): + # Melhora listas dentro de pontos a melhorar + prefix = " * " if in_list else "* " + linhas_formatadas.append(prefix + linha_strip[2:]) + in_list = True + elif linha_strip.startswith("a)") or linha_strip.startswith("b)") or linha_strip.startswith("c)") or linha_strip.startswith("d)") or linha_strip.startswith("e)"): + linhas_formatadas.append(f" * {linha_strip}") + in_list = True else: - texto_formatado += f"{linha}\n" - if "Nota Final" in texto_formatado: - import re - nota_pattern = r"### Nota Final.*?(\d+[.,]\d+|\d+)" - match = re.search(nota_pattern, texto_formatado, re.DOTALL) - if match: - nota = match.group(1) - texto_formatado = texto_formatado.replace(f"### ⭐ Nota Final\n\n{nota}", f"### ⭐ Nota Final: {nota}/10\n\n") - return texto_formatado + linhas_formatadas.append(linha) + if not linha_strip.startswith(("*", "- ", "a)", "b)", "c)", "d)", "e)"," ")): # Reseta se não for item de lista + in_list = False + + + # Tenta formatar a nota + texto_final = "\n".join(linhas_formatadas) + import re + nota_pattern = r"(\*\*Nota Final:?\*\*\s*)(\d{1,2}[,.]\d{1}|\d{1,2})" + match = re.search(nota_pattern, texto_final, re.IGNORECASE) + if match: + nota = match.group(2).replace(",",".") + texto_final = re.sub(nota_pattern, f"\\1**{nota}/10**", texto_final, count=1, flags=re.IGNORECASE) + + return texto_final + + +def fornecer_dica_contextual(texto_peticao, tipo_peticao): + if not texto_peticao or not texto_peticao.strip(): + return "Digite algo na petição para receber dicas..." + + dicas_relevantes = [] + texto_lower = texto_peticao.lower() + + # Adiciona dicas gerais primeiro + for chave, dica in DICAS_OAB_MELHORADO.items(): + if chave in ["Clareza e Concisão", "Fundamentação Legal", "Coerência Argumentativa", "Revisão Ortográfica e Gramatical", "Formatação"]: + dicas_relevantes.append(f"💡 **{chave}:** {dica}") + + # Adiciona dicas específicas do tipo (se houver palavras-chave) + keywords_por_tipo = { + "Petição Inicial": ["endereçamento", "qualificação", "fatos", "fundamentos", "pedidos", "valor da causa", "provas", "audiência", "citação", "custas"], + "Contestação": ["preliminar", "mérito", "impugnação", "fatos", "improcedência", "provas", "honorários"], + "Reconvenção": ["reconvinte", "reconvindo", "conexão", "pedidos reconvencionais"], + "Impugnação à Contestação": ["réplica", "preliminar", "fatos novos", "documentos", "ratifica"], + "Embargos de Declaração": ["omissão", "contradição", "obscuridade", "erro material", "decisão embargada"], + "Recurso de Apelação": ["sentença", "reforma", "anulação", "dialeticidade", "preparo", "tribunal", "razões"], + "Agravo de Instrumento": ["decisão interlocutória", "tribunal", "cabimento", "urgência", "efeito suspensivo", "peças obrigatórias"], + "Petição de Tutela Provisória": ["urgência", "evidência", "liminar", "periculum", "fumus boni", "probabilidade", "risco"], + } -def extrair_peticao_corrigida(correcao_completa): - try: - if "### Petição Corrigida" in correcao_completa: - inicio = correcao_completa.index("### Petição Corrigida") + len("### Petição Corrigida") - peticao_texto = correcao_completa[inicio:].strip() - fim = peticao_texto.find("###") if "###" in peticao_texto else len(peticao_texto) - return peticao_texto[:fim].strip() - return "⚠️ Seção '### Petição Corrigida' não encontrada." - except Exception as e: - logger.error(f"Erro ao extrair petição corrigida: {str(e)}") - return f"❌ Erro ao extrair petição: {str(e)}" + if tipo_peticao in keywords_por_tipo: + for keyword in keywords_por_tipo[tipo_peticao]: + if keyword in texto_lower: + # Busca dicas que contenham a keyword ou o tipo no nome + for chave, dica in DICAS_OAB_MELHORADO.items(): + chave_lower = chave.lower() + if f"({tipo_peticao.split()[0].lower()})" in chave_lower or keyword in chave_lower: + dica_formatada = f"💡 **{chave}:** {dica}" + if dica_formatada not in dicas_relevantes: + dicas_relevantes.append(dica_formatada) -def fornecer_dica_contextual(texto): - dicas = [f"💡 {chave}: {dica}" for chave, dica in DICAS_OAB_MELHORADO.items() if chave.lower() in texto.lower()] - return "\n".join(dicas) if dicas else "💡 Escreva mais para receber dicas." + if not dicas_relevantes: + return "💡 Continue escrevendo ou selecione o tipo de peça para dicas mais específicas." -def calcular_progresso(): - if not HISTORICO: - return "📊 Nenhum progresso registrado." - notas = [] - for entry in HISTORICO: - for line in entry["correcao_completa"].split("\n"): - if "Nota Final" in line: - try: - nota = float(line.split()[-1].split("/")[0].replace(",", ".")) - notas.append(nota) - except: - continue - media = sum(notas) / len(notas) if notas else 0 - tendencia = "⬆️ Melhorando" if len(notas) > 1 and notas[-1] > notas[-2] else "⬇️ Estável ou piorando" - return f"📊 Progresso: Média {media:.1f}/10 | Tentativas: {len(notas)} | Tendência: {tendencia}" - -def comparar_peticao_melhorada(caso, peticao_texto, correcao): - if not caso: - return "⚠️ Forneça um caso antes de comparar.", "", "", "", "" - peticao = peticao_texto if peticao_texto else (HISTORICO[-1]["peticao"] if HISTORICO else "") - if not peticao: - return "⚠️ Forneça uma petição para comparar ou corrija uma antes.", "", "", "", "" - if not correcao: - return "⚠️ Corrija a petição antes de comparar.", "", "", "", "" - ultima_correcao = HISTORICO[-1]["correcao_completa"] if HISTORICO else "" - if not ultima_correcao: - return "⚠️ Nenhuma correção disponível no histórico.", "", "", "", "" - - peticao_corrigida = extrair_peticao_corrigida(ultima_correcao) - if "⚠️" in peticao_corrigida or "❌" in peticao_corrigida: - return peticao_corrigida, "", "", "", "" - - orig_lines = peticao.split('\n') - corr_lines = peticao_corrigida.split('\n') - start_orig = next((i for i, line in enumerate(orig_lines) if "AÇÃO" in line.upper() or "EXCELENTÍSSIMO" in line.upper()), 0) - start_corr = next((i for i, line in enumerate(corr_lines) if "AÇÃO" in line.upper() or "EXCELENTÍSSIMO" in line.upper()), 0) - - orig_body = "\n".join(orig_lines[start_orig:]).strip() - corr_body = "\n".join(corr_lines[start_corr:]).strip() - - diff = difflib.ndiff(orig_body.splitlines(), corr_body.splitlines()) - - resultado_diff = [] + # Limita o número de dicas exibidas + return "\n\n".join(dicas_relevantes[:5]) # Mostra no máximo 5 dicas + +# --- Funções de Comparação (Diff) --- + +def calcular_diff_interno(texto_original, texto_corrigido): + """Calcula a diferença e retorna HTML, resumo e críticas.""" + if not texto_original or not texto_corrigido or texto_corrigido.startswith("⚠️") or texto_corrigido.startswith("❌"): + return "","","" # Retorna vazio se não houver o que comparar + + orig_lines = texto_original.splitlines() + corr_lines = texto_corrigido.splitlines() + + diff = difflib.ndiff(orig_lines, corr_lines) + + resultado_diff_html = [] criticas = [] + linhas_removidas = 0 + linhas_adicionadas = 0 + + keywords_criticas_removidas = ["invenção", "identificação", "erro crasso", "fundamento incorreto"] + keywords_criticas_adicionadas = ["custas", "art. 319", "art. 337", "art. 1022", "art. 1015", "art. 300", "fumus", "periculum", "pedido essencial", "preliminar obrigatória"] + for linha in diff: + texto = linha[2:] + texto_escaped = escape(texto) if linha.startswith(' '): - texto = linha[2:] - resultado_diff.append(f"