Update app.py
Browse files
app.py
CHANGED
|
@@ -18,26 +18,9 @@ from ratelimiter import RateLimiter
|
|
| 18 |
import difflib
|
| 19 |
from html import escape
|
| 20 |
from weasyprint import HTML # Para gerar PDF com cores
|
| 21 |
-
import shelve
|
| 22 |
-
import logging
|
| 23 |
-
import re
|
| 24 |
-
|
| 25 |
-
# Configuração de logging
|
| 26 |
-
logging.basicConfig(filename='app.log', level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s')
|
| 27 |
|
| 28 |
load_dotenv()
|
| 29 |
|
| 30 |
-
# Verificação de dependências
|
| 31 |
-
def verificar_dependencias():
|
| 32 |
-
dependencias = {
|
| 33 |
-
"gradio": gr, "requests": requests, "pdfplumber": pdfplumber_open,
|
| 34 |
-
"pytesseract": pytesseract, "cv2": cv2, "weasyprint": HTML
|
| 35 |
-
}
|
| 36 |
-
for nome, modulo in dependencias.items():
|
| 37 |
-
if modulo is None:
|
| 38 |
-
raise ImportError(f"❌ Módulo {nome} não encontrado. Instale com 'pip install {nome}'.")
|
| 39 |
-
verificar_dependencias()
|
| 40 |
-
|
| 41 |
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
|
| 42 |
if not GEMINI_API_KEY:
|
| 43 |
raise ValueError("❌ A chave GEMINI_API_KEY não foi encontrada. Configure-a como Secret no Hugging Face Spaces.")
|
|
@@ -49,36 +32,44 @@ CONFIG = {
|
|
| 49 |
"retry_delay": 5,
|
| 50 |
"timeout": 60,
|
| 51 |
"temperatura": 0.2,
|
| 52 |
-
"max_tokens":
|
| 53 |
}
|
| 54 |
|
| 55 |
rate_limiter = RateLimiter(max_calls=60, period=60)
|
| 56 |
|
| 57 |
DICAS_OAB_MELHORADO = {
|
| 58 |
-
"Endereçamento": "Em relações de consumo, prefira o foro do domicílio do autor (Art. 101, I, CDC).
|
| 59 |
-
|
| 60 |
-
"
|
| 61 |
-
|
| 62 |
-
"
|
| 63 |
-
|
| 64 |
-
"
|
| 65 |
-
|
| 66 |
-
"
|
| 67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
}
|
| 69 |
|
| 70 |
HISTORICO = []
|
|
|
|
| 71 |
|
| 72 |
def preprocess_image_for_ocr(img):
|
| 73 |
try:
|
| 74 |
gray = img.convert('L')
|
| 75 |
np_img = np.array(gray)
|
| 76 |
-
|
| 77 |
-
enhanced = clahe.apply(np_img)
|
| 78 |
-
_, thresh = cv2.threshold(enhanced, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
|
| 79 |
return Image.fromarray(thresh)
|
| 80 |
except Exception as e:
|
| 81 |
-
logging.error(f"Erro no pré-processamento de imagem: {str(e)}")
|
| 82 |
return f"❌ Erro no pré-processamento: {str(e)}"
|
| 83 |
|
| 84 |
def extract_text_from_pdf(file_path_or_obj):
|
|
@@ -114,7 +105,6 @@ def extract_text_from_pdf(file_path_or_obj):
|
|
| 114 |
text += f"[Erro na página {i+1}: {str(e)}]\n"
|
| 115 |
return text if text.strip() else "⚠️ Não foi possível extrair texto do PDF."
|
| 116 |
except Exception as e:
|
| 117 |
-
logging.error(f"Erro ao processar PDF: {str(e)}")
|
| 118 |
return f"❌ Erro ao processar o PDF: {str(e)}"
|
| 119 |
|
| 120 |
def extract_text_from_docx(file_path_or_obj):
|
|
@@ -142,7 +132,6 @@ def extract_text_from_docx(file_path_or_obj):
|
|
| 142 |
text += "\n"
|
| 143 |
return text if text.strip() else "⚠️ Não foi possível extrair texto do DOCX."
|
| 144 |
except Exception as e:
|
| 145 |
-
logging.error(f"Erro ao processar DOCX: {str(e)}")
|
| 146 |
return f"❌ Erro ao processar o DOCX: {str(e)}"
|
| 147 |
|
| 148 |
def extract_text_from_txt(file_path_or_obj):
|
|
@@ -167,11 +156,9 @@ def extract_text_from_txt(file_path_or_obj):
|
|
| 167 |
continue
|
| 168 |
return "❌ Não foi possível decodificar o arquivo TXT."
|
| 169 |
except Exception as e:
|
| 170 |
-
logging.error(f"Erro ao processar TXT: {str(e)}")
|
| 171 |
return f"❌ Erro ao processar o TXT: {str(e)}"
|
| 172 |
|
| 173 |
def process_file(file_obj):
|
| 174 |
-
"""Processa arquivos PDF, DOCX ou TXT e extrai texto."""
|
| 175 |
if file_obj is None:
|
| 176 |
return "Nenhum arquivo enviado."
|
| 177 |
try:
|
|
@@ -192,7 +179,6 @@ def process_file(file_obj):
|
|
| 192 |
else:
|
| 193 |
return "❌ Erro: Objeto de arquivo inválido."
|
| 194 |
except Exception as e:
|
| 195 |
-
logging.error(f"Erro ao processar arquivo: {str(e)}")
|
| 196 |
return f"❌ Erro ao processar o arquivo: {str(e)}"
|
| 197 |
|
| 198 |
def calcular_pontuacao(enderecamento, fundamentos, pedidos, fechamento):
|
|
@@ -219,85 +205,19 @@ def calcular_pontuacao(enderecamento, fundamentos, pedidos, fechamento):
|
|
| 219 |
return f"{tabela_formatada}\n\n**Nota Final:** {nota_final:.2f} / 10,00"
|
| 220 |
|
| 221 |
def validar_peticao_basica(peticao):
|
| 222 |
-
"""Valida os elementos básicos da petição com base no Art. 319 do CPC e critérios OAB."""
|
| 223 |
problemas = []
|
| 224 |
-
|
| 225 |
-
peticao_lower = peticao.lower()
|
| 226 |
-
|
| 227 |
-
if "EXCELENTÍSSIMO" not in peticao_upper or "JUIZ" not in peticao_upper:
|
| 228 |
problemas.append("⚠️ Endereçamento ausente ou incompleto (Art. 319, I, CPC).")
|
| 229 |
-
if "
|
| 230 |
-
problemas.append("⚠️
|
| 231 |
-
if "
|
| 232 |
-
problemas.append("⚠️ Exposição de fatos ou fundamentos ausente (Art. 319, III, CPC).")
|
| 233 |
-
if "ART." not in peticao_upper or "CPC" not in peticao_upper:
|
| 234 |
-
problemas.append("⚠️ Fundamentação jurídica não menciona artigos ou CPC (Art. 319, III, CPC).")
|
| 235 |
-
if "REQUER" not in peticao_upper and "PEDE" not in peticao_upper:
|
| 236 |
-
problemas.append("⚠️ Pedidos não especificados ou ausentes (Art. 319, IV, CPC).")
|
| 237 |
-
if "DÁ-SE À CAUSA O VALOR" not in peticao_upper:
|
| 238 |
problemas.append("⚠️ Valor da causa não especificado (Art. 319, V, CPC).")
|
| 239 |
-
if "
|
| 240 |
-
problemas.append("⚠️ Valor da causa sem quantia monetária explícita (Art. 319, V, CPC).")
|
| 241 |
-
if "PROVA" not in peticao_upper and "TESTEMUNHA" not in peticao_upper and "DOCUMENTO" not in peticao_upper:
|
| 242 |
-
problemas.append("⚠️ Provas pretendidas não especificadas (Art. 319, VI, CPC).")
|
| 243 |
-
if "AUDIÊNCIA" not in peticao_upper and "CONCILIAÇÃO" not in peticao_upper:
|
| 244 |
-
problemas.append("⚠️ Opção sobre audiência de conciliação não mencionada (Art. 319, VII, CPC).")
|
| 245 |
-
if "CITAÇÃO" not in peticao_upper:
|
| 246 |
-
problemas.append("⚠️ Pedido de citação do réu ausente.")
|
| 247 |
-
if "juntada da guia de custas" not in peticao_lower:
|
| 248 |
problemas.append("⚠️ Pedido de juntada da guia de custas ausente (-1,0 ponto).")
|
| 249 |
-
if "
|
| 250 |
-
problemas.append("⚠️ Fechamento formal
|
| 251 |
-
if "NESTES TERMOS," in peticao_upper:
|
| 252 |
-
problemas.append("⚠️ Uso incorreto de vírgula em 'Nestes termos, pede deferimento'.")
|
| 253 |
-
if "ADVOGADO" not in peticao_upper or "OAB" not in peticao_upper:
|
| 254 |
-
problemas.append("⚠️ Fechamento sem 'ADVOGADO' ou 'OAB'.")
|
| 255 |
-
if "LOCAL E DATA" not in peticao_upper:
|
| 256 |
-
problemas.append("⚠️ Fechamento sem 'Local e data'.")
|
| 257 |
-
if any(id in peticao_upper for id in ["CPF", "RG", "MATRÍCULA"]):
|
| 258 |
-
problemas.append("⚠️ Identificação indevida do candidato detectada (nota máxima 3,0).")
|
| 259 |
-
if "OAB/" in peticao_upper or any(char.isdigit() for char in peticao.split("OAB")[-1] if "OAB" in peticao_upper):
|
| 260 |
-
problemas.append("⚠️ Identificação específica na OAB (ex.: número ou estado) detectada (nota 0,0).")
|
| 261 |
-
|
| 262 |
return problemas
|
| 263 |
|
| 264 |
-
# Função comentada (removida da execução, mantida para referência)
|
| 265 |
-
"""
|
| 266 |
-
def verificar_coerencia_caso_peticao(caso, peticao):
|
| 267 |
-
incoerencias = []
|
| 268 |
-
caso_lower = caso.lower()
|
| 269 |
-
peticao_lower = peticao.lower()
|
| 270 |
-
|
| 271 |
-
nomes_caso = [word for word in caso.split() if word.istitle() and len(word) > 2]
|
| 272 |
-
nomes_peticao = [word for word in peticao.split() if word.istitle() and len(word) > 2]
|
| 273 |
-
for nome in nomes_caso:
|
| 274 |
-
if nome.lower() not in peticao_lower:
|
| 275 |
-
incoerencias.append(f"⚠️ Nome '{nome}' do caso não mencionado na petição (suspeita de omissão).")
|
| 276 |
-
for frase in peticao.split("."):
|
| 277 |
-
if frase.strip() and not any(palavra.lower() in caso_lower for palavra in frase.split() if len(palavra) > 3):
|
| 278 |
-
incoerencias.append(f"⚠️ Frase suspeita de invenção: '{frase.strip()}' (não consta no caso).")
|
| 279 |
-
valores_caso = re.findall(r"R\$\s*\d+(?:\.\d+)?(?:,\d+)?", caso)
|
| 280 |
-
valores_peticao = re.findall(r"R\$\s*\d+(?:\.\d+)?(?:,\d+)?", peticao)
|
| 281 |
-
for valor in valores_peticao:
|
| 282 |
-
if valor not in valores_caso:
|
| 283 |
-
incoerencias.append(f"⚠️ Valor '{valor}' na petição não consta no caso (suspeita de invenção).")
|
| 284 |
-
|
| 285 |
-
return incoerencias
|
| 286 |
-
"""
|
| 287 |
-
|
| 288 |
-
def validar_resposta_api(correcao_completa):
|
| 289 |
-
"""Valida a qualidade da resposta da API."""
|
| 290 |
-
problemas_api = []
|
| 291 |
-
if "Nota Final" not in correcao_completa:
|
| 292 |
-
problemas_api.append("⚠️ Resposta da API sem 'Nota Final'.")
|
| 293 |
-
if "Pontos a Melhorar" not in correcao_completa:
|
| 294 |
-
problemas_api.append("⚠️ Resposta da API sem 'Pontos a Melhorar'.")
|
| 295 |
-
if "Análise Específica dos Requisitos" not in correcao_completa:
|
| 296 |
-
problemas_api.append("⚠️ Resposta da API sem 'Análise Específica dos Requisitos'.")
|
| 297 |
-
if "### Petição Corrigida" not in correcao_completa:
|
| 298 |
-
problemas_api.append("⚠️ Resposta da API sem 'Petição Corrigida'.")
|
| 299 |
-
return problemas_api
|
| 300 |
-
|
| 301 |
def estruturar_prompt_correcao(caso, peticao, modo_treinamento=False):
|
| 302 |
"""Estrutura um prompt avançado e especializado para correção de petições."""
|
| 303 |
base_prompt = f"""
|
|
@@ -338,7 +258,7 @@ def estruturar_prompt_correcao(caso, peticao, modo_treinamento=False):
|
|
| 338 |
- **Consistência Fática**: Compare cada fato narrado na petição com o caso; destaque omissões ou distorções com base no Art. 319, III, CPC.
|
| 339 |
- **Fundamentação Jurídica**: Exija citação de pelo menos um artigo específico (ex.: Art. 186, CC para responsabilidade civil) e avalie a pertinência; penalize generalidades com -0,15 ponto por item vago.
|
| 340 |
- **Danos Morais e Materiais**: Se o caso sugerir danos, verifique se os pedidos estão quantificados e fundamentados (ex.: Súmula 387/STJ para danos morais); penalize omissões com -0,20 ponto.
|
| 341 |
-
- **Linguagem Técnica**: Avalie a formalidade e precisão do vocabulário jurídico; penalize informalidades ou erros gramaticais (ex.: "vossa excelência" em minúsculas)
|
| 342 |
- **Precedentes**: Inclua referências a súmulas ou decisões relevantes (ex.: Súmula 297/STJ para CDC) quando aplicável no caso.
|
| 343 |
|
| 344 |
**Pontuação Detalhada:**
|
|
@@ -451,33 +371,9 @@ def gerar_caso_ficticio():
|
|
| 451 |
continue
|
| 452 |
return f"❌ Erro ao chamar a API: {e}"
|
| 453 |
except Exception as e:
|
| 454 |
-
logging.error(f"Erro inesperado ao gerar caso: {str(e)}")
|
| 455 |
return f"❌ Erro inesperado: {str(e)}"
|
| 456 |
|
| 457 |
-
def sanitize_input(text):
|
| 458 |
-
"""Sanitiza entradas para evitar injeção."""
|
| 459 |
-
return escape(text.strip()) if text else ""
|
| 460 |
-
|
| 461 |
-
def salvar_cache(chave, valor):
|
| 462 |
-
"""Salva correção no cache persistente."""
|
| 463 |
-
try:
|
| 464 |
-
with shelve.open("correcao_cache.db") as db:
|
| 465 |
-
db[chave] = valor
|
| 466 |
-
except Exception as e:
|
| 467 |
-
logging.error(f"Erro ao salvar no cache: {str(e)}")
|
| 468 |
-
|
| 469 |
-
def carregar_cache(chave):
|
| 470 |
-
"""Carrega correção do cache persistente."""
|
| 471 |
-
try:
|
| 472 |
-
with shelve.open("correcao_cache.db") as db:
|
| 473 |
-
return db.get(chave)
|
| 474 |
-
except Exception as e:
|
| 475 |
-
logging.error(f"Erro ao carregar do cache: {str(e)}")
|
| 476 |
-
return None
|
| 477 |
-
|
| 478 |
def corrigir_peticao(caso, peticao_texto=None, peticao_arquivo=None, modo_treinamento=False):
|
| 479 |
-
"""Corrige uma petição inicial cível com base em critérios da OAB."""
|
| 480 |
-
caso = sanitize_input(caso)
|
| 481 |
if not caso.strip():
|
| 482 |
return "⚠️ Forneça o caso da banca."
|
| 483 |
if peticao_arquivo is not None:
|
|
@@ -485,7 +381,7 @@ def corrigir_peticao(caso, peticao_texto=None, peticao_arquivo=None, modo_treina
|
|
| 485 |
if peticao_conteudo.startswith("❌") or peticao_conteudo.startswith("⚠️"):
|
| 486 |
return peticao_conteudo
|
| 487 |
else:
|
| 488 |
-
peticao_conteudo =
|
| 489 |
if not peticao_conteudo.strip():
|
| 490 |
return "⚠️ Forneça uma petição."
|
| 491 |
|
|
@@ -493,9 +389,8 @@ def corrigir_peticao(caso, peticao_texto=None, peticao_arquivo=None, modo_treina
|
|
| 493 |
aviso_validacao = f"### Validação Inicial\n\n" + "\n".join(problemas) + "\n\n" if problemas else ""
|
| 494 |
|
| 495 |
cache_key = f"{caso[:50]}|{peticao_conteudo[:50]}|{modo_treinamento}"
|
| 496 |
-
|
| 497 |
-
|
| 498 |
-
return aviso_validacao + cached_result
|
| 499 |
|
| 500 |
prompt = estruturar_prompt_correcao(caso, peticao_conteudo, modo_treinamento)
|
| 501 |
payload = {
|
|
@@ -521,9 +416,6 @@ def corrigir_peticao(caso, peticao_texto=None, peticao_arquivo=None, modo_treina
|
|
| 521 |
continue
|
| 522 |
return aviso_validacao + "❌ A API retornou uma resposta vazia. Tente novamente."
|
| 523 |
correcao_completa = response_data["candidates"][0]["content"]["parts"][0]["text"]
|
| 524 |
-
problemas_api = validar_resposta_api(correcao_completa)
|
| 525 |
-
if problemas_api:
|
| 526 |
-
return aviso_validacao + f"### Problemas na Resposta da API\n\n" + "\n".join(problemas_api) + "\n\n⚠️ Tente novamente."
|
| 527 |
if "### Petição Corrigida" in correcao_completa:
|
| 528 |
analise_fim = correcao_completa.index("### Petição Corrigida")
|
| 529 |
analise = correcao_completa[:analise_fim].strip()
|
|
@@ -536,9 +428,9 @@ def corrigir_peticao(caso, peticao_texto=None, peticao_arquivo=None, modo_treina
|
|
| 536 |
"peticao": peticao_conteudo,
|
| 537 |
"correcao_completa": correcao_completa
|
| 538 |
})
|
| 539 |
-
if len(HISTORICO) > 10:
|
| 540 |
HISTORICO.pop(0)
|
| 541 |
-
|
| 542 |
return aviso_validacao + correcao_formatada
|
| 543 |
except requests.Timeout:
|
| 544 |
if tentativa < CONFIG["max_retry_attempts"] - 1:
|
|
@@ -551,7 +443,6 @@ def corrigir_peticao(caso, peticao_texto=None, peticao_arquivo=None, modo_treina
|
|
| 551 |
continue
|
| 552 |
return aviso_validacao + f"❌ Erro ao chamar a API: {e}"
|
| 553 |
except Exception as e:
|
| 554 |
-
logging.error(f"Erro inesperado ao corrigir petição: {str(e)}")
|
| 555 |
return aviso_validacao + f"❌ Erro inesperado: {str(e)}"
|
| 556 |
|
| 557 |
def formatar_correcao(texto):
|
|
@@ -575,6 +466,7 @@ def formatar_correcao(texto):
|
|
| 575 |
else:
|
| 576 |
texto_formatado += f"{linha}\n"
|
| 577 |
if "Nota Final" in texto_formatado:
|
|
|
|
| 578 |
nota_pattern = r"### Nota Final.*?(\d+[.,]\d+|\d+)"
|
| 579 |
match = re.search(nota_pattern, texto_formatado, re.DOTALL)
|
| 580 |
if match:
|
|
@@ -590,9 +482,9 @@ def extrair_peticao_corrigida(correcao_completa):
|
|
| 590 |
fim = peticao_texto.find("###") if "###" in peticao_texto else len(peticao_texto)
|
| 591 |
peticao_texto = peticao_texto[:fim].strip()
|
| 592 |
return peticao_texto
|
| 593 |
-
|
|
|
|
| 594 |
except Exception as e:
|
| 595 |
-
logging.error(f"Erro ao extrair petição corrigida: {str(e)}")
|
| 596 |
return f"❌ Erro ao extrair petição corrigida: {str(e)}"
|
| 597 |
|
| 598 |
def fornecer_dica_contextual(texto):
|
|
@@ -712,7 +604,6 @@ def exportar_diferencas_html(caso, peticao_texto, correcao):
|
|
| 712 |
HTML(string=html_content).write_pdf(temp_path)
|
| 713 |
return temp_path
|
| 714 |
except Exception as e:
|
| 715 |
-
logging.error(f"Erro ao gerar PDF: {str(e)}")
|
| 716 |
return f"❌ Erro ao gerar PDF: {str(e)}"
|
| 717 |
finally:
|
| 718 |
if 'temp_path' in locals() and os.path.exists(temp_path):
|
|
@@ -837,7 +728,7 @@ def interface_gradio():
|
|
| 837 |
inputs=[caso_input, peticao_texto, resultado_output],
|
| 838 |
outputs=[download_file]
|
| 839 |
).then(
|
| 840 |
-
fn=lambda x: [gr.update(visible=True, value=x if not x.startswith("❌") else None), gr.update(visible=True)],
|
| 841 |
outputs=[download_file, download_btn]
|
| 842 |
)
|
| 843 |
|
|
|
|
| 18 |
import difflib
|
| 19 |
from html import escape
|
| 20 |
from weasyprint import HTML # Para gerar PDF com cores
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
load_dotenv()
|
| 23 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
|
| 25 |
if not GEMINI_API_KEY:
|
| 26 |
raise ValueError("❌ A chave GEMINI_API_KEY não foi encontrada. Configure-a como Secret no Hugging Face Spaces.")
|
|
|
|
| 32 |
"retry_delay": 5,
|
| 33 |
"timeout": 60,
|
| 34 |
"temperatura": 0.2,
|
| 35 |
+
"max_tokens": 4096
|
| 36 |
}
|
| 37 |
|
| 38 |
rate_limiter = RateLimiter(max_calls=60, period=60)
|
| 39 |
|
| 40 |
DICAS_OAB_MELHORADO = {
|
| 41 |
+
"Endereçamento": "Em relações de consumo, prefira o foro do domicílio do autor (Art. 101, I, CDC). "
|
| 42 |
+
"Exemplo: 'Excelentíssimo Senhor Juiz de Direito da Vara Cível da Comarca de [Cidade do Autor]'.",
|
| 43 |
+
"Qualificação": "Mencione nome, estado civil (se informado), endereço (use 'Rua XXX' se não fornecido). "
|
| 44 |
+
"Dica: Sempre verifique se todos os dados das partes estão corretos e completos.",
|
| 45 |
+
"Fatos": "Descreva os fatos cronologicamente, sem inventar informações. "
|
| 46 |
+
"Exemplo: 'Em [data], o autor celebrou contrato com o réu para a prestação de serviços de [descrição]'.",
|
| 47 |
+
"Pedidos": "Seja específico nos pedidos (ex.: 'condenação ao pagamento de R$ X'). "
|
| 48 |
+
"Dica: Inclua pedidos de danos morais e materiais, se aplicável, com base em precedentes.",
|
| 49 |
+
"Custas": "Inclua 'Requer a juntada da guia de custas devidamente recolhida'. "
|
| 50 |
+
"Dica: Nunca se esqueça de mencionar o recolhimento das custas para evitar a extinção do processo.",
|
| 51 |
+
"Fundamentação": "Baseie-se em artigos do CPC ou CDC e, se possível, em súmulas do STJ (ex.: Súmula 387 para danos morais). "
|
| 52 |
+
"Exemplo: 'Conforme o artigo 186 do Código Civil, a responsabilidade civil pode ser caracterizada'.",
|
| 53 |
+
"Fechamento": "Finalize com 'Nestes termos pede deferimento', sem vírgula. "
|
| 54 |
+
"Dica: Evite erros de pontuação que podem comprometer a formalidade do documento.",
|
| 55 |
+
"Estratégia Jurídica": "Utilize precedentes e súmulas para fortalecer sua argumentação. "
|
| 56 |
+
"Exemplo: 'Conforme a Súmula 387 do STJ, os danos morais devem ser comprovados e quantificados'.",
|
| 57 |
+
"Provas": "Especifique as provas que pretende produzir. "
|
| 58 |
+
"Dica: Inclua testemunhas, documentos e perícias, se necessário, para comprovar os fatos alegados.",
|
| 59 |
+
"Contra-argumentação": "Antecipe possíveis defesas da parte contrária e prepare respostas. "
|
| 60 |
+
"Dica: Considere os argumentos que a outra parte pode usar e esteja preparado para refutá-los."
|
| 61 |
}
|
| 62 |
|
| 63 |
HISTORICO = []
|
| 64 |
+
CORRECAO_CACHE = {}
|
| 65 |
|
| 66 |
def preprocess_image_for_ocr(img):
|
| 67 |
try:
|
| 68 |
gray = img.convert('L')
|
| 69 |
np_img = np.array(gray)
|
| 70 |
+
_, thresh = cv2.threshold(np_img, 150, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
|
|
|
|
|
|
|
| 71 |
return Image.fromarray(thresh)
|
| 72 |
except Exception as e:
|
|
|
|
| 73 |
return f"❌ Erro no pré-processamento: {str(e)}"
|
| 74 |
|
| 75 |
def extract_text_from_pdf(file_path_or_obj):
|
|
|
|
| 105 |
text += f"[Erro na página {i+1}: {str(e)}]\n"
|
| 106 |
return text if text.strip() else "⚠️ Não foi possível extrair texto do PDF."
|
| 107 |
except Exception as e:
|
|
|
|
| 108 |
return f"❌ Erro ao processar o PDF: {str(e)}"
|
| 109 |
|
| 110 |
def extract_text_from_docx(file_path_or_obj):
|
|
|
|
| 132 |
text += "\n"
|
| 133 |
return text if text.strip() else "⚠️ Não foi possível extrair texto do DOCX."
|
| 134 |
except Exception as e:
|
|
|
|
| 135 |
return f"❌ Erro ao processar o DOCX: {str(e)}"
|
| 136 |
|
| 137 |
def extract_text_from_txt(file_path_or_obj):
|
|
|
|
| 156 |
continue
|
| 157 |
return "❌ Não foi possível decodificar o arquivo TXT."
|
| 158 |
except Exception as e:
|
|
|
|
| 159 |
return f"❌ Erro ao processar o TXT: {str(e)}"
|
| 160 |
|
| 161 |
def process_file(file_obj):
|
|
|
|
| 162 |
if file_obj is None:
|
| 163 |
return "Nenhum arquivo enviado."
|
| 164 |
try:
|
|
|
|
| 179 |
else:
|
| 180 |
return "❌ Erro: Objeto de arquivo inválido."
|
| 181 |
except Exception as e:
|
|
|
|
| 182 |
return f"❌ Erro ao processar o arquivo: {str(e)}"
|
| 183 |
|
| 184 |
def calcular_pontuacao(enderecamento, fundamentos, pedidos, fechamento):
|
|
|
|
| 205 |
return f"{tabela_formatada}\n\n**Nota Final:** {nota_final:.2f} / 10,00"
|
| 206 |
|
| 207 |
def validar_peticao_basica(peticao):
|
|
|
|
| 208 |
problemas = []
|
| 209 |
+
if "EXCELENTÍSSIMO" not in peticao.upper():
|
|
|
|
|
|
|
|
|
|
| 210 |
problemas.append("⚠️ Endereçamento ausente ou incompleto (Art. 319, I, CPC).")
|
| 211 |
+
if "Art. 319" not in peticao and "CPC" not in peticao:
|
| 212 |
+
problemas.append("⚠️ Fundamentação jurídica não menciona o CPC (Art. 319).")
|
| 213 |
+
if "Dá-se à causa o valor" not in peticao:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 214 |
problemas.append("⚠️ Valor da causa não especificado (Art. 319, V, CPC).")
|
| 215 |
+
if "juntada da guia de custas" not in peticao.lower():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
problemas.append("⚠️ Pedido de juntada da guia de custas ausente (-1,0 ponto).")
|
| 217 |
+
if "Nestes termos" not in peticao:
|
| 218 |
+
problemas.append("⚠️ Fechamento formal ausente.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
return problemas
|
| 220 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 221 |
def estruturar_prompt_correcao(caso, peticao, modo_treinamento=False):
|
| 222 |
"""Estrutura um prompt avançado e especializado para correção de petições."""
|
| 223 |
base_prompt = f"""
|
|
|
|
| 258 |
- **Consistência Fática**: Compare cada fato narrado na petição com o caso; destaque omissões ou distorções com base no Art. 319, III, CPC.
|
| 259 |
- **Fundamentação Jurídica**: Exija citação de pelo menos um artigo específico (ex.: Art. 186, CC para responsabilidade civil) e avalie a pertinência; penalize generalidades com -0,15 ponto por item vago.
|
| 260 |
- **Danos Morais e Materiais**: Se o caso sugerir danos, verifique se os pedidos estão quantificados e fundamentados (ex.: Súmula 387/STJ para danos morais); penalize omissões com -0,20 ponto.
|
| 261 |
+
- **Linguagem Técnica**: Avalie a formalidade e precisão do vocabulário jurídico; penalize informalidades ou erros gramaticais (ex.: "vossa excelência" em minúsculas) con -0,05 ponto por ocorrência.
|
| 262 |
- **Precedentes**: Inclua referências a súmulas ou decisões relevantes (ex.: Súmula 297/STJ para CDC) quando aplicável no caso.
|
| 263 |
|
| 264 |
**Pontuação Detalhada:**
|
|
|
|
| 371 |
continue
|
| 372 |
return f"❌ Erro ao chamar a API: {e}"
|
| 373 |
except Exception as e:
|
|
|
|
| 374 |
return f"❌ Erro inesperado: {str(e)}"
|
| 375 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 376 |
def corrigir_peticao(caso, peticao_texto=None, peticao_arquivo=None, modo_treinamento=False):
|
|
|
|
|
|
|
| 377 |
if not caso.strip():
|
| 378 |
return "⚠️ Forneça o caso da banca."
|
| 379 |
if peticao_arquivo is not None:
|
|
|
|
| 381 |
if peticao_conteudo.startswith("❌") or peticao_conteudo.startswith("⚠️"):
|
| 382 |
return peticao_conteudo
|
| 383 |
else:
|
| 384 |
+
peticao_conteudo = peticao_texto or ""
|
| 385 |
if not peticao_conteudo.strip():
|
| 386 |
return "⚠️ Forneça uma petição."
|
| 387 |
|
|
|
|
| 389 |
aviso_validacao = f"### Validação Inicial\n\n" + "\n".join(problemas) + "\n\n" if problemas else ""
|
| 390 |
|
| 391 |
cache_key = f"{caso[:50]}|{peticao_conteudo[:50]}|{modo_treinamento}"
|
| 392 |
+
if cache_key in CORRECAO_CACHE:
|
| 393 |
+
return aviso_validacao + CORRECAO_CACHE[cache_key]
|
|
|
|
| 394 |
|
| 395 |
prompt = estruturar_prompt_correcao(caso, peticao_conteudo, modo_treinamento)
|
| 396 |
payload = {
|
|
|
|
| 416 |
continue
|
| 417 |
return aviso_validacao + "❌ A API retornou uma resposta vazia. Tente novamente."
|
| 418 |
correcao_completa = response_data["candidates"][0]["content"]["parts"][0]["text"]
|
|
|
|
|
|
|
|
|
|
| 419 |
if "### Petição Corrigida" in correcao_completa:
|
| 420 |
analise_fim = correcao_completa.index("### Petição Corrigida")
|
| 421 |
analise = correcao_completa[:analise_fim].strip()
|
|
|
|
| 428 |
"peticao": peticao_conteudo,
|
| 429 |
"correcao_completa": correcao_completa
|
| 430 |
})
|
| 431 |
+
if len(HISTORICO) > 10: # Limite de histórico
|
| 432 |
HISTORICO.pop(0)
|
| 433 |
+
CORRECAO_CACHE[cache_key] = correcao_formatada
|
| 434 |
return aviso_validacao + correcao_formatada
|
| 435 |
except requests.Timeout:
|
| 436 |
if tentativa < CONFIG["max_retry_attempts"] - 1:
|
|
|
|
| 443 |
continue
|
| 444 |
return aviso_validacao + f"❌ Erro ao chamar a API: {e}"
|
| 445 |
except Exception as e:
|
|
|
|
| 446 |
return aviso_validacao + f"❌ Erro inesperado: {str(e)}"
|
| 447 |
|
| 448 |
def formatar_correcao(texto):
|
|
|
|
| 466 |
else:
|
| 467 |
texto_formatado += f"{linha}\n"
|
| 468 |
if "Nota Final" in texto_formatado:
|
| 469 |
+
import re
|
| 470 |
nota_pattern = r"### Nota Final.*?(\d+[.,]\d+|\d+)"
|
| 471 |
match = re.search(nota_pattern, texto_formatado, re.DOTALL)
|
| 472 |
if match:
|
|
|
|
| 482 |
fim = peticao_texto.find("###") if "###" in peticao_texto else len(peticao_texto)
|
| 483 |
peticao_texto = peticao_texto[:fim].strip()
|
| 484 |
return peticao_texto
|
| 485 |
+
else:
|
| 486 |
+
return "⚠️ A seção '### Petição Corrigida' não foi encontrada na resposta da API. Verifique a resposta e tente novamente."
|
| 487 |
except Exception as e:
|
|
|
|
| 488 |
return f"❌ Erro ao extrair petição corrigida: {str(e)}"
|
| 489 |
|
| 490 |
def fornecer_dica_contextual(texto):
|
|
|
|
| 604 |
HTML(string=html_content).write_pdf(temp_path)
|
| 605 |
return temp_path
|
| 606 |
except Exception as e:
|
|
|
|
| 607 |
return f"❌ Erro ao gerar PDF: {str(e)}"
|
| 608 |
finally:
|
| 609 |
if 'temp_path' in locals() and os.path.exists(temp_path):
|
|
|
|
| 728 |
inputs=[caso_input, peticao_texto, resultado_output],
|
| 729 |
outputs=[download_file]
|
| 730 |
).then(
|
| 731 |
+
fn=lambda x: [gr.update(visible=True, value=x) if isinstance(x, str) and not x.startswith("❌") else gr.update(visible=True, value=None, label=f"Erro: {x}" if x else "Erro desconhecido"), gr.update(visible=True)],
|
| 732 |
outputs=[download_file, download_btn]
|
| 733 |
)
|
| 734 |
|