Update app.py
Browse files
app.py
CHANGED
|
@@ -17,7 +17,7 @@ from datetime import datetime
|
|
| 17 |
from ratelimiter import RateLimiter
|
| 18 |
import difflib
|
| 19 |
from html import escape
|
| 20 |
-
from weasyprint import HTML
|
| 21 |
|
| 22 |
load_dotenv()
|
| 23 |
|
|
@@ -32,7 +32,7 @@ CONFIG = {
|
|
| 32 |
"retry_delay": 5,
|
| 33 |
"timeout": 60,
|
| 34 |
"temperatura": 0.2,
|
| 35 |
-
"max_tokens":
|
| 36 |
}
|
| 37 |
|
| 38 |
rate_limiter = RateLimiter(max_calls=60, period=60)
|
|
@@ -53,6 +53,124 @@ DICAS_OAB_MELHORADO = {
|
|
| 53 |
HISTORICO = []
|
| 54 |
CORRECAO_CACHE = {}
|
| 55 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
def estruturar_prompt_analise(caso, peticao, modo_treinamento=False):
|
| 57 |
"""Estrutura o prompt apenas para a análise da petição."""
|
| 58 |
base_prompt = f"""
|
|
@@ -87,7 +205,7 @@ def estruturar_prompt_analise(caso, peticao, modo_treinamento=False):
|
|
| 87 |
5. Não penalize por informações ausentes no caso original, mas alerte se a omissão compromete a petição.
|
| 88 |
6. A frase "Nestes termos, pede deferimento" com vírgula é incorreta; corrija para "Nestes termos pede deferimento" e penalize com -0,2 ponto, destacando o erro formal.
|
| 89 |
7. A petição deve finalizar com "Local e data.\nADVOGADO.\nOAB." sem identificação do aluno (nome, número ou estado na OAB); se houver identificação específica (ex.: "OAB/SP" ou "12345"), zere a nota (0,0) e explique o motivo.
|
| 90 |
-
|
| 91 |
**Instruções Especiais para Análise Avançada:**
|
| 92 |
- **Competência**: Avalie o foro indicado 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); penalize erros com -0,10 ponto e justifique com jurisprudência (ex.: STJ, REsp 1.234.567).
|
| 93 |
- **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.
|
|
@@ -133,61 +251,27 @@ def estruturar_prompt_analise(caso, peticao, modo_treinamento=False):
|
|
| 133 |
return base_prompt
|
| 134 |
|
| 135 |
def estruturar_prompt_peticao_corrigida(caso, peticao):
|
| 136 |
-
"""Estrutura o prompt para a petição corrigida
|
| 137 |
return f"""
|
| 138 |
**Caso Fornecido:**
|
| 139 |
{caso}
|
| 140 |
**Petição do Aluno:**
|
| 141 |
{peticao}
|
| 142 |
### Petição Corrigida
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
11. **Fechamento formal**: Finalize EXATAMENTE com "Local e data.\nADVOGADO.\nOAB." sem identificação específica (ex.: "OAB/SP" ou "12345" resultam em nota zero). A frase "Nestes termos, pede deferimento" com vírgula DEVE SER corrigida para "Nestes termos pede deferimento" (sem vírgula), conforme exigência formal da OAB.
|
| 156 |
-
|
| 157 |
-
**Instruções Obrigatórias**:
|
| 158 |
-
- Não omita nenhum dos 11 itens acima. Cada um é essencial e deve ser verificado e corrigido se necessário.
|
| 159 |
-
- Siga estritamente as regras formais da OAB, especialmente a correção da vírgula em "Nestes termos pede deferimento".
|
| 160 |
-
- Baseie-se exclusivamente no caso fornecido e na petição do aluno, sem inventar fatos ou dados.
|
| 161 |
-
- Gere a petição completa, incluindo todas as seções (endereçamento, qualificação, fatos, direito, pedidos, valor da causa, provas, fechamento).
|
| 162 |
"""
|
| 163 |
|
| 164 |
-
def pos_corrigir_peticao(peticao_corrigida):
|
| 165 |
-
"""Pós-processamento para garantir que todas as regras sejam aplicadas."""
|
| 166 |
-
# Garantir a correção da vírgula
|
| 167 |
-
if "Nestes termos, pede deferimento" in peticao_corrigida:
|
| 168 |
-
peticao_corrigida = peticao_corrigida.replace("Nestes termos, pede deferimento", "Nestes termos pede deferimento")
|
| 169 |
-
|
| 170 |
-
# Garantir o pedido de juntada da guia de custas
|
| 171 |
-
if "juntada da guia de custas" not in peticao_corrigida.lower():
|
| 172 |
-
peticao_corrigida = peticao_corrigida.replace("Nestes termos pede deferimento", "Requer a juntada da guia de custas devidamente recolhida.\nNestes termos pede deferimento")
|
| 173 |
-
|
| 174 |
-
# Garantir o fechamento formal correto
|
| 175 |
-
if not peticao_corrigida.strip().endswith("Local e data.\nADVOGADO.\nOAB."):
|
| 176 |
-
linhas = peticao_corrigida.split('\n')
|
| 177 |
-
while linhas and not linhas[-1].strip(): # Remove linhas em branco no final
|
| 178 |
-
linhas.pop()
|
| 179 |
-
if len(linhas) >= 3:
|
| 180 |
-
linhas[-3:] = ["Local e data.", "ADVOGADO.", "OAB."]
|
| 181 |
-
else:
|
| 182 |
-
linhas.extend(["Local e data.", "ADVOGADO.", "OAB."])
|
| 183 |
-
peticao_corrigida = "\n".join(linhas)
|
| 184 |
-
|
| 185 |
-
# Garantir opção por audiência explícita
|
| 186 |
-
if "audiência de conciliação" not in peticao_corrigida.lower():
|
| 187 |
-
peticao_corrigida = peticao_corrigida.replace("Nestes termos pede deferimento", "Requer a designação de audiência de conciliação, nos termos do Art. 319, VII, CPC.\nNestes termos pede deferimento")
|
| 188 |
-
|
| 189 |
-
return peticao_corrigida
|
| 190 |
-
|
| 191 |
def chamar_api(prompt):
|
| 192 |
payload = {
|
| 193 |
"contents": [{"parts": [{"text": prompt}]}],
|
|
@@ -257,10 +341,6 @@ def corrigir_peticao(caso, peticao_texto=None, peticao_arquivo=None, modo_treina
|
|
| 257 |
if "❌" in peticao_corrigida or "⏱️" in peticao_corrigida:
|
| 258 |
return aviso_validacao + analise_formatada + "\n\n" + peticao_corrigida
|
| 259 |
|
| 260 |
-
# Pós-correção para garantir conformidade
|
| 261 |
-
peticao_corrigida = pos_corrigir_peticao(peticao_corrigida)
|
| 262 |
-
print("Debug: Petição corrigida após pós-processamento:\n", peticao_corrigida[:1000]) # Debug para verificar a saída
|
| 263 |
-
|
| 264 |
correcao_completa = analise + "\n\n" + peticao_corrigida
|
| 265 |
HISTORICO.append({
|
| 266 |
"data": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
@@ -273,17 +353,178 @@ def corrigir_peticao(caso, peticao_texto=None, peticao_arquivo=None, modo_treina
|
|
| 273 |
CORRECAO_CACHE[cache_key] = analise_formatada
|
| 274 |
return aviso_validacao + analise_formatada
|
| 275 |
|
| 276 |
-
def
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 287 |
|
| 288 |
def interface_gradio():
|
| 289 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
@@ -447,7 +688,7 @@ def gerar_caso_ficticio():
|
|
| 447 |
def estruturar_prompt_geracao_caso():
|
| 448 |
return """
|
| 449 |
Crie um caso fictício detalhado para uma petição inicial cível, similar ao exemplo:
|
| 450 |
-
"Ana, modelo profissional, residente em Manaus, viajou para São Paulo, para o casamento de sua filha. Para lavar, pintar seus cabelos e realizar um penteado para o casamento, Ana procurou os serviços de João Macedo, cabeleireiro e dono do salão de beleza 'Hair', sediado na cidade de São Paulo, que lhe cobrou R$500,00 (quinhentos reais) pela prestação do serviço. Após lavar os cabelos de Ana, João aplicou-lhe uma tintura da marca francesa ABC, importada pela empresa Brasil Connection Ltda, sediada na cidade de Curitiba (PR). Meia hora após a aplicação da tintura, Ana sofreu uma reação alérgica, que demandou atendimento médico-hospitalar, no valor de R$1
|
| 451 |
O caso deve:
|
| 452 |
1. Conter uma situação clara de violação de direitos que justifique uma ação cível
|
| 453 |
2. Incluir valores específicos de danos materiais e/ou morais
|
|
@@ -471,8 +712,6 @@ def validar_peticao_basica(peticao):
|
|
| 471 |
problemas.append("⚠️ Endereçamento ausente ou incompleto (Art. 319, I, CPC).")
|
| 472 |
if "Art. 319" not in peticao and "CPC" not in peticao:
|
| 473 |
problemas.append("⚠️ Fundamentação jurídica não menciona o CPC (Art. 319).")
|
| 474 |
-
if "Dá-se à causa o valor" not in peticao:
|
| 475 |
-
problemas.append("⚠️ Fundamentação jurídica não menciona o CPC (Art. 319).")
|
| 476 |
if "Dá-se à causa o valor" not in peticao:
|
| 477 |
problemas.append("⚠️ Valor da causa não especificado (Art. 319, V, CPC).")
|
| 478 |
if "juntada da guia de custas" not in peticao.lower():
|
|
@@ -481,12 +720,5 @@ def validar_peticao_basica(peticao):
|
|
| 481 |
problemas.append("⚠️ Fechamento formal ausente.")
|
| 482 |
return problemas
|
| 483 |
|
| 484 |
-
def calcular_progresso():
|
| 485 |
-
total_correcoes = len(HISTORICO)
|
| 486 |
-
if total_correcoes == 0:
|
| 487 |
-
return "Nenhuma correção realizada até o momento."
|
| 488 |
-
else:
|
| 489 |
-
return f"Total de correções realizadas: {total_correcoes}"
|
| 490 |
-
|
| 491 |
app = interface_gradio()
|
| 492 |
-
app.launch()
|
|
|
|
| 17 |
from ratelimiter import RateLimiter
|
| 18 |
import difflib
|
| 19 |
from html import escape
|
| 20 |
+
from weasyprint import HTML # Para gerar PDF com cores
|
| 21 |
|
| 22 |
load_dotenv()
|
| 23 |
|
|
|
|
| 32 |
"retry_delay": 5,
|
| 33 |
"timeout": 60,
|
| 34 |
"temperatura": 0.2,
|
| 35 |
+
"max_tokens": 8000 # Mantido em 8000, mas dividido em duas chamadas
|
| 36 |
}
|
| 37 |
|
| 38 |
rate_limiter = RateLimiter(max_calls=60, period=60)
|
|
|
|
| 53 |
HISTORICO = []
|
| 54 |
CORRECAO_CACHE = {}
|
| 55 |
|
| 56 |
+
def preprocess_image_for_ocr(img):
|
| 57 |
+
try:
|
| 58 |
+
gray = img.convert('L')
|
| 59 |
+
np_img = np.array(gray)
|
| 60 |
+
_, thresh = cv2.threshold(np_img, 150, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
|
| 61 |
+
return Image.fromarray(thresh)
|
| 62 |
+
except Exception as e:
|
| 63 |
+
return f"❌ Erro no pré-processamento: {str(e)}"
|
| 64 |
+
|
| 65 |
+
def extract_text_from_pdf(file_path_or_obj):
|
| 66 |
+
try:
|
| 67 |
+
if isinstance(file_path_or_obj, str):
|
| 68 |
+
with open(file_path_or_obj, 'rb') as f:
|
| 69 |
+
text = ""
|
| 70 |
+
with pdfplumber_open(f) as pdf:
|
| 71 |
+
for page in pdf.pages:
|
| 72 |
+
text += (page.extract_text() or "") + "\n"
|
| 73 |
+
if not text.strip():
|
| 74 |
+
images = pdfplumber_open(f).pages
|
| 75 |
+
for i, image in enumerate(images):
|
| 76 |
+
try:
|
| 77 |
+
img = image.to_image()
|
| 78 |
+
preprocessed_img = preprocess_image_for_ocr(img)
|
| 79 |
+
text += pytesseract.image_to_string(preprocessed_img) + "\n"
|
| 80 |
+
except Exception as e:
|
| 81 |
+
text += f"[Erro na página {i+1}: {str(e)}]\n"
|
| 82 |
+
else:
|
| 83 |
+
text = ""
|
| 84 |
+
with pdfplumber_open(file_path_or_obj) as pdf:
|
| 85 |
+
for page in pdf.pages:
|
| 86 |
+
text += (page.extract_text() or "") + "\n"
|
| 87 |
+
if not text.strip():
|
| 88 |
+
images = pdfplumber_open(file_path_or_obj).pages
|
| 89 |
+
for i, image in enumerate(images):
|
| 90 |
+
try:
|
| 91 |
+
img = image.to_image()
|
| 92 |
+
preprocessed_img = preprocess_image_for_ocr(img)
|
| 93 |
+
text += pytesseract.image_to_string(preprocessed_img) + "\n"
|
| 94 |
+
except Exception as e:
|
| 95 |
+
text += f"[Erro na página {i+1}: {str(e)}]\n"
|
| 96 |
+
return text if text.strip() else "⚠️ Não foi possível extrair texto do PDF."
|
| 97 |
+
except Exception as e:
|
| 98 |
+
return f"❌ Erro ao processar o PDF: {str(e)}"
|
| 99 |
+
|
| 100 |
+
def extract_text_from_docx(file_path_or_obj):
|
| 101 |
+
try:
|
| 102 |
+
if isinstance(file_path_or_obj, str):
|
| 103 |
+
with open(file_path_or_obj, 'rb') as f:
|
| 104 |
+
doc = docx.Document(f)
|
| 105 |
+
text = ""
|
| 106 |
+
for para in doc.paragraphs:
|
| 107 |
+
text += para.text + "\n"
|
| 108 |
+
for table in doc.tables:
|
| 109 |
+
for row in table.rows:
|
| 110 |
+
for cell in row.cells:
|
| 111 |
+
text += cell.text + " "
|
| 112 |
+
text += "\n"
|
| 113 |
+
else:
|
| 114 |
+
doc = docx.Document(file_path_or_obj)
|
| 115 |
+
text = ""
|
| 116 |
+
for para in doc.paragraphs:
|
| 117 |
+
text += para.text + "\n"
|
| 118 |
+
for table in doc.tables:
|
| 119 |
+
for row in table.rows:
|
| 120 |
+
for cell in row.cells:
|
| 121 |
+
text += cell.text + " "
|
| 122 |
+
text += "\n"
|
| 123 |
+
return text if text.strip() else "⚠️ Não foi possível extrair texto do DOCX."
|
| 124 |
+
except Exception as e:
|
| 125 |
+
return f"❌ Erro ao processar o DOCX: {str(e)}"
|
| 126 |
+
|
| 127 |
+
def extract_text_from_txt(file_path_or_obj):
|
| 128 |
+
try:
|
| 129 |
+
if isinstance(file_path_or_obj, str):
|
| 130 |
+
with open(file_path_or_obj, 'rb') as f:
|
| 131 |
+
content = f.read()
|
| 132 |
+
encodings = ['utf-8', 'latin-1', 'cp1252', 'iso-8859-1']
|
| 133 |
+
for encoding in encodings:
|
| 134 |
+
try:
|
| 135 |
+
return content.decode(encoding)
|
| 136 |
+
except UnicodeDecodeError:
|
| 137 |
+
continue
|
| 138 |
+
return "❌ Não foi possível decodificar o arquivo TXT."
|
| 139 |
+
else:
|
| 140 |
+
content = file_path_or_obj.read()
|
| 141 |
+
encodings = ['utf-8', 'latin-1', 'cp1252', 'iso-8859-1']
|
| 142 |
+
for encoding in encodings:
|
| 143 |
+
try:
|
| 144 |
+
return content.decode(encoding)
|
| 145 |
+
except UnicodeDecodeError:
|
| 146 |
+
continue
|
| 147 |
+
return "❌ Não foi possível decodificar o arquivo TXT."
|
| 148 |
+
except Exception as e:
|
| 149 |
+
return f"❌ Erro ao processar o TXT: {str(e)}"
|
| 150 |
+
|
| 151 |
+
def process_file(file_obj):
|
| 152 |
+
if file_obj is None:
|
| 153 |
+
return "Nenhum arquivo enviado."
|
| 154 |
+
try:
|
| 155 |
+
if hasattr(file_obj, 'name'):
|
| 156 |
+
file_path = file_obj.name
|
| 157 |
+
filename = os.path.basename(file_path).lower()
|
| 158 |
+
file_size = os.path.getsize(file_path)
|
| 159 |
+
if file_size > 10 * 1024 * 1024:
|
| 160 |
+
return "⚠️ O arquivo é muito grande. Máx. 10 MB."
|
| 161 |
+
if filename.endswith('.pdf'):
|
| 162 |
+
return extract_text_from_pdf(file_path)
|
| 163 |
+
elif filename.endswith('.docx'):
|
| 164 |
+
return extract_text_from_docx(file_path)
|
| 165 |
+
elif filename.endswith('.txt'):
|
| 166 |
+
return extract_text_from_txt(file_path)
|
| 167 |
+
else:
|
| 168 |
+
return "⚠️ Formato de arquivo não suportado. Use PDF, DOCX ou TXT."
|
| 169 |
+
else:
|
| 170 |
+
return "❌ Erro: Objeto de arquivo inválido."
|
| 171 |
+
except Exception as e:
|
| 172 |
+
return f"❌ Erro ao processar o arquivo: {str(e)}"
|
| 173 |
+
|
| 174 |
def estruturar_prompt_analise(caso, peticao, modo_treinamento=False):
|
| 175 |
"""Estrutura o prompt apenas para a análise da petição."""
|
| 176 |
base_prompt = f"""
|
|
|
|
| 205 |
5. Não penalize por informações ausentes no caso original, mas alerte se a omissão compromete a petição.
|
| 206 |
6. A frase "Nestes termos, pede deferimento" com vírgula é incorreta; corrija para "Nestes termos pede deferimento" e penalize com -0,2 ponto, destacando o erro formal.
|
| 207 |
7. A petição deve finalizar com "Local e data.\nADVOGADO.\nOAB." sem identificação do aluno (nome, número ou estado na OAB); se houver identificação específica (ex.: "OAB/SP" ou "12345"), zere a nota (0,0) e explique o motivo.
|
| 208 |
+
|
| 209 |
**Instruções Especiais para Análise Avançada:**
|
| 210 |
- **Competência**: Avalie o foro indicado 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); penalize erros com -0,10 ponto e justifique com jurisprudência (ex.: STJ, REsp 1.234.567).
|
| 211 |
- **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.
|
|
|
|
| 251 |
return base_prompt
|
| 252 |
|
| 253 |
def estruturar_prompt_peticao_corrigida(caso, peticao):
|
| 254 |
+
"""Estrutura o prompt apenas para a petição corrigida."""
|
| 255 |
return f"""
|
| 256 |
**Caso Fornecido:**
|
| 257 |
{caso}
|
| 258 |
**Petição do Aluno:**
|
| 259 |
{peticao}
|
| 260 |
### Petição Corrigida
|
| 261 |
+
Certifique-se de incluir esta seção exatamente como '### Petição Corrigida'. Forneça a petição do aluno reescrita com todas as correções aplicadas, incluindo partes omitidas (como pedidos ou custas) e ajustando erros identificados com base nos seguintes critérios:
|
| 262 |
+
1. Endereçamento correto (com foro justificado por CPC ou CDC).
|
| 263 |
+
2. Qualificação completa (Art. 319, II, CPC; use "XXX" para dados ausentes).
|
| 264 |
+
3. Fatos claros e fiéis ao caso (Art. 319, III, CPC).
|
| 265 |
+
4. Fundamentação jurídica robusta (cite CPC, CDC, CC e súmulas aplicáveis, como Súmula 387/STJ para danos morais).
|
| 266 |
+
5. Pedidos específicos e quantificados (Art. 319, IV, CPC).
|
| 267 |
+
6. Valor da causa realista (Art. 319, V, CPC).
|
| 268 |
+
7. Provas detalhadas (Art. 319, VI, CPC; ex.: "prova documental e testemunhal").
|
| 269 |
+
8. Opção por audiência explícita (Art. 319, VII, CPC).
|
| 270 |
+
9. Pedido de citação formal (ex.: "via postal, nos termos do Art. 246, I, CPC").
|
| 271 |
+
10. Juntada da guia de custas com redação precisa.
|
| 272 |
+
11. Fechamento formal com "Local e data.\nADVOGADO.\nOAB." sem identificação específica.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
"""
|
| 274 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 275 |
def chamar_api(prompt):
|
| 276 |
payload = {
|
| 277 |
"contents": [{"parts": [{"text": prompt}]}],
|
|
|
|
| 341 |
if "❌" in peticao_corrigida or "⏱️" in peticao_corrigida:
|
| 342 |
return aviso_validacao + analise_formatada + "\n\n" + peticao_corrigida
|
| 343 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 344 |
correcao_completa = analise + "\n\n" + peticao_corrigida
|
| 345 |
HISTORICO.append({
|
| 346 |
"data": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
|
|
| 353 |
CORRECAO_CACHE[cache_key] = analise_formatada
|
| 354 |
return aviso_validacao + analise_formatada
|
| 355 |
|
| 356 |
+
def formatar_correcao(texto):
|
| 357 |
+
linhas = texto.split('\n')
|
| 358 |
+
texto_formatado = ""
|
| 359 |
+
for linha in linhas:
|
| 360 |
+
if "Análise Geral" in linha:
|
| 361 |
+
texto_formatado += f"### ℹ️ {linha.strip()}\n\n"
|
| 362 |
+
elif "Nota Final" in linha:
|
| 363 |
+
texto_formatado += f"### ⭐ {linha.strip()}\n\n"
|
| 364 |
+
elif "Pontos Positivos" in linha:
|
| 365 |
+
texto_formatado += f"### ✅ {linha.strip()}\n\n"
|
| 366 |
+
elif "Pontos a Melhorar" in linha:
|
| 367 |
+
texto_formatado += f"### ⚠️ {linha.strip()}\n\n"
|
| 368 |
+
elif "- Invenção de fatos" in linha:
|
| 369 |
+
texto_formatado += f"**🔴 {linha.strip()} (-5,0 pontos)**\n"
|
| 370 |
+
elif linha.strip().startswith('-') and len(linha) > 2 and any(termo in linha for termo in ["Endereçamento", "Qualificação", "Fatos", "Pedidos", "Valor", "Provas"]):
|
| 371 |
+
texto_formatado += f"**{linha.strip()}**\n"
|
| 372 |
+
elif "Explicação" in linha or "Sugestão" in linha or "Dica" in linha or "Exemplo" in linha:
|
| 373 |
+
texto_formatado += f"*{linha.strip()}*\n"
|
| 374 |
+
else:
|
| 375 |
+
texto_formatado += f"{linha}\n"
|
| 376 |
+
if "Nota Final" in texto_formatado:
|
| 377 |
+
import re
|
| 378 |
+
nota_pattern = r"### Nota Final.*?(\d+[.,]\d+|\d+)"
|
| 379 |
+
match = re.search(nota_pattern, texto_formatado, re.DOTALL)
|
| 380 |
+
if match:
|
| 381 |
+
nota = match.group(1)
|
| 382 |
+
texto_formatado = texto_formatado.replace(f"### ⭐ Nota Final\n\n{nota}", f"### ⭐ Nota Final: {nota}/10\n\n")
|
| 383 |
+
return texto_formatado
|
| 384 |
+
|
| 385 |
+
def extrair_peticao_corrigida(correcao_completa):
|
| 386 |
+
try:
|
| 387 |
+
if "### Petição Corrigida" in correcao_completa:
|
| 388 |
+
inicio = correcao_completa.index("### Petição Corrigida") + len("### Petição Corrigida")
|
| 389 |
+
peticao_texto = correcao_completa[inicio:].strip()
|
| 390 |
+
fim = peticao_texto.find("###") if "###" in peticao_texto else len(peticao_texto)
|
| 391 |
+
peticao_texto = peticao_texto[:fim].strip()
|
| 392 |
+
return peticao_texto
|
| 393 |
+
else:
|
| 394 |
+
linhas = correcao_completa.split('\n')
|
| 395 |
+
inicio_peticao = -1
|
| 396 |
+
for i, linha in enumerate(linhas):
|
| 397 |
+
if "EXCELENTÍSSIMO" in linha.upper() and i > len(linhas) // 2:
|
| 398 |
+
inicio_peticao = i
|
| 399 |
+
break
|
| 400 |
+
if inicio_peticao != -1:
|
| 401 |
+
peticao_texto = "\n".join(linhas[inicio_peticao:]).strip()
|
| 402 |
+
return peticao_texto
|
| 403 |
+
return "⚠️ A seção '### Petição Corrigida' não foi encontrada na resposta da API. Verifique a resposta e tente novamente."
|
| 404 |
+
except Exception as e:
|
| 405 |
+
return f"❌ Erro ao extrair petição corrigida: {str(e)}"
|
| 406 |
+
|
| 407 |
+
def fornecer_dica_contextual(texto):
|
| 408 |
+
dicas = [f"💡 {chave}: {dica}" for chave, dica in DICAS_OAB_MELHORADO.items() if chave.lower() in texto.lower()]
|
| 409 |
+
return "\n".join(dicas) if dicas else "💡 Escreva mais para receber dicas."
|
| 410 |
+
|
| 411 |
+
def calcular_progresso():
|
| 412 |
+
if not HISTORICO:
|
| 413 |
+
return "📊 Nenhum progresso registrado."
|
| 414 |
+
notas = []
|
| 415 |
+
for entry in HISTORICO:
|
| 416 |
+
for line in entry["correcao_completa"].split("\n"):
|
| 417 |
+
if "Nota Final" in line:
|
| 418 |
+
try:
|
| 419 |
+
nota = float(line.split()[-1].split("/")[0].replace(",", "."))
|
| 420 |
+
notas.append(nota)
|
| 421 |
+
except:
|
| 422 |
+
continue
|
| 423 |
+
media = sum(notas) / len(notas) if notas else 0
|
| 424 |
+
tendencia = "⬆️ Melhorando" if len(notas) > 1 and notas[-1] > notas[-2] else "⬇️ Estável ou piorando"
|
| 425 |
+
return f"📊 Progresso: Média {media:.1f}/10 | Tentativas: {len(notas)} | Tendência: {tendencia}"
|
| 426 |
+
|
| 427 |
+
def comparar_peticao_melhorada(caso, peticao_texto, correcao):
|
| 428 |
+
if not caso:
|
| 429 |
+
return "⚠️ Forneça um caso antes de comparar.", "", "", "", ""
|
| 430 |
+
peticao = peticao_texto if peticao_texto else (HISTORICO[-1]["peticao"] if HISTORICO else "")
|
| 431 |
+
if not peticao:
|
| 432 |
+
return "⚠️ Forneça uma petição para comparar ou corrija uma antes.", "", "", "", ""
|
| 433 |
+
if not correcao:
|
| 434 |
+
return "⚠️ Corrija a petição antes de comparar.", "", "", "", ""
|
| 435 |
+
ultima_correcao = HISTORICO[-1]["correcao_completa"] if HISTORICO else ""
|
| 436 |
+
if not ultima_correcao:
|
| 437 |
+
return "⚠️ Nenhuma correção disponível no histórico.", "", "", "", ""
|
| 438 |
+
|
| 439 |
+
peticao_corrigida = extrair_peticao_corrigida(ultima_correcao)
|
| 440 |
+
if "⚠️" in peticao_corrigida or "❌" in peticao_corrigida:
|
| 441 |
+
return peticao_corrigida, "", "", "", ""
|
| 442 |
+
|
| 443 |
+
orig_lines = peticao.split('\n')
|
| 444 |
+
corr_lines = peticao_corrigida.split('\n')
|
| 445 |
+
start_orig = next((i for i, line in enumerate(orig_lines) if "AÇÃO" in line.upper() or "EXCELENTÍSSIMO" in line.upper()), 0)
|
| 446 |
+
start_corr = next((i for i, line in enumerate(corr_lines) if "AÇÃO" in line.upper() or "EXCELENTÍSSIMO" in line.upper()), 0)
|
| 447 |
+
|
| 448 |
+
orig_body = "\n".join(orig_lines[start_orig:]).strip()
|
| 449 |
+
corr_body = "\n".join(corr_lines[start_corr:]).strip()
|
| 450 |
+
|
| 451 |
+
diff = difflib.ndiff(orig_body.splitlines(), corr_body.splitlines())
|
| 452 |
+
|
| 453 |
+
resultado_diff = []
|
| 454 |
+
criticas = []
|
| 455 |
+
for linha in diff:
|
| 456 |
+
if linha.startswith(' '):
|
| 457 |
+
texto = linha[2:]
|
| 458 |
+
resultado_diff.append(f"<div class='linha-igual'>{escape(texto)}</div>")
|
| 459 |
+
elif linha.startswith('- '):
|
| 460 |
+
texto = linha[2:]
|
| 461 |
+
if any(kw in texto.lower() for kw in ["invenção", "custas", "art. 319"]):
|
| 462 |
+
criticas.append(f"🔴 REMOVIDO: {texto} (inconsistente ou incorreto)")
|
| 463 |
+
resultado_diff.append(f"<div class='linha-removida'><del>{escape(texto)}</del></div>")
|
| 464 |
+
elif linha.startswith('+ '):
|
| 465 |
+
texto = linha[2:]
|
| 466 |
+
if any(kw in texto.lower() for kw in ["custas", "art. 319"]):
|
| 467 |
+
criticas.append(f"🟢 ADICIONADO: {texto} (essencial para conformidade)")
|
| 468 |
+
resultado_diff.append(f"<div class='linha-adicionada'><ins>{escape(texto)}</ins></div>")
|
| 469 |
+
elif linha.startswith('? '):
|
| 470 |
+
continue
|
| 471 |
+
|
| 472 |
+
total_removidas = len([linha for linha in resultado_diff if "linha-removida" in linha])
|
| 473 |
+
total_adicionadas = len([linha for linha in resultado_diff if "linha-adicionada" in linha])
|
| 474 |
+
|
| 475 |
+
resumo_estatistico = f"""
|
| 476 |
+
Linhas Removidas: {total_removidas}
|
| 477 |
+
Linhas Adicionadas: {total_adicionadas}
|
| 478 |
+
Diferença Líquida: {total_adicionadas - total_removidas} linhas
|
| 479 |
+
"""
|
| 480 |
+
|
| 481 |
+
criticas_texto = "\n".join(criticas) if criticas else "Nenhuma mudança crítica detectada."
|
| 482 |
+
|
| 483 |
+
visualizacao_detalhada = f"""
|
| 484 |
+
<style>
|
| 485 |
+
.linha-igual {{ color: #000000; margin: 2px 0; }}
|
| 486 |
+
.linha-removida {{ color: #ffffff; background-color: #b30000; margin: 2px 0; padding: 2px; }}
|
| 487 |
+
.linha-adicionada {{ color: #ffffff; background-color: #006600; margin: 2px 0; padding: 2px; }}
|
| 488 |
+
</style>
|
| 489 |
+
{"".join(resultado_diff)}
|
| 490 |
+
"""
|
| 491 |
+
return peticao, peticao_corrigida, resumo_estatistico, criticas_texto, visualizacao_detalhada
|
| 492 |
+
|
| 493 |
+
def exportar_diferencas_html(caso, peticao_texto, correcao):
|
| 494 |
+
_, _, resumo_estatistico, criticas_texto, visualizacao_detalhada = comparar_peticao_melhorada(caso, peticao_texto, correcao)
|
| 495 |
+
html_content = f"""
|
| 496 |
+
<html>
|
| 497 |
+
<head>
|
| 498 |
+
<title>Diferenças na Petição</title>
|
| 499 |
+
<style>
|
| 500 |
+
body {{ font-family: Arial, sans-serif; margin: 20px; }}
|
| 501 |
+
.linha-igual {{ color: #000000; margin: 2px 0; }}
|
| 502 |
+
.linha-removida {{ color: #ffffff; background-color: #b30000; margin: 2px 0; padding: 2px; }}
|
| 503 |
+
.linha-adicionada {{ color: #ffffff; background-color: #006600; margin: 2px 0; padding: 2px; }}
|
| 504 |
+
h2 {{ color: #333; }}
|
| 505 |
+
pre {{ white-space: pre-wrap; }}
|
| 506 |
+
</style>
|
| 507 |
+
</head>
|
| 508 |
+
<body>
|
| 509 |
+
<h2>Estatísticas</h2>
|
| 510 |
+
<pre>{resumo_estatistico}</pre>
|
| 511 |
+
<h2>Mudanças Críticas</h2>
|
| 512 |
+
<pre>{criticas_texto}</pre>
|
| 513 |
+
<h2>Alterações Detalhadas</h2>
|
| 514 |
+
{visualizacao_detalhada}
|
| 515 |
+
</body>
|
| 516 |
+
</html>
|
| 517 |
+
"""
|
| 518 |
+
try:
|
| 519 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp:
|
| 520 |
+
temp_path = tmp.name
|
| 521 |
+
HTML(string=html_content).write_pdf(temp_path)
|
| 522 |
+
return temp_path
|
| 523 |
+
except Exception as e:
|
| 524 |
+
return f"❌ Erro ao gerar PDF: {str(e)}"
|
| 525 |
+
finally:
|
| 526 |
+
if 'temp_path' in locals() and os.path.exists(temp_path):
|
| 527 |
+
os.unlink(temp_path)
|
| 528 |
|
| 529 |
def interface_gradio():
|
| 530 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
|
|
| 688 |
def estruturar_prompt_geracao_caso():
|
| 689 |
return """
|
| 690 |
Crie um caso fictício detalhado para uma petição inicial cível, similar ao exemplo:
|
| 691 |
+
"Ana, modelo profissional, residente em Manaus, viajou para São Paulo, para o casamento de sua filha. Para lavar, pintar seus cabelos e realizar um penteado para o casamento, Ana procurou os serviços de João Macedo, cabeleireiro e dono do salão de beleza 'Hair', sediado na cidade de São Paulo, que lhe cobrou R$500,00 (quinhentos reais) pela prestação do serviço. Após lavar os cabelos de Ana, João aplicou-lhe uma tintura da marca francesa ABC, importada pela empresa Brasil Connection Ltda, sediada na cidade de Curitiba (PR). Meia hora após a aplicação da tintura, Ana sofreu uma reação alérgica, que demandou atendimento médico-hospitalar, no valor de R$1.000,00 (um mil reais), bem como dois dias de absoluto repouso que impossibilitou sua presença no casamento de sua filha. Além disso, perdeu grande parte de seu cabelo, tendo permanecido com manchas em seu rosto por dois meses, perdendo um ensaio fotográfico, para o qual já havia sido contratada, pelo valor de R$50.000,00 (cinquenta mil reais). Posteriormente constatou-se que a tintura utilizada continha substâncias químicas extremamente perigosas à vida e à saúde das pessoas e a fabricante ABC já havia sido condenada pela justiça francesa a encerrar a fabricação e comercialização do produto. Indignada com os danos sofridos, Ana procura um advogado para pleitear o devido ressarcimento."
|
| 692 |
O caso deve:
|
| 693 |
1. Conter uma situação clara de violação de direitos que justifique uma ação cível
|
| 694 |
2. Incluir valores específicos de danos materiais e/ou morais
|
|
|
|
| 712 |
problemas.append("⚠️ Endereçamento ausente ou incompleto (Art. 319, I, CPC).")
|
| 713 |
if "Art. 319" not in peticao and "CPC" not in peticao:
|
| 714 |
problemas.append("⚠️ Fundamentação jurídica não menciona o CPC (Art. 319).")
|
|
|
|
|
|
|
| 715 |
if "Dá-se à causa o valor" not in peticao:
|
| 716 |
problemas.append("⚠️ Valor da causa não especificado (Art. 319, V, CPC).")
|
| 717 |
if "juntada da guia de custas" not in peticao.lower():
|
|
|
|
| 720 |
problemas.append("⚠️ Fechamento formal ausente.")
|
| 721 |
return problemas
|
| 722 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 723 |
app = interface_gradio()
|
| 724 |
+
app.launch()
|