Update app.py
Browse files
app.py
CHANGED
|
@@ -16,30 +16,24 @@ import numpy as np
|
|
| 16 |
from datetime import datetime
|
| 17 |
from ratelimiter import RateLimiter
|
| 18 |
|
| 19 |
-
# Carrega variáveis de ambiente de um arquivo .env (opcional para dev local)
|
| 20 |
load_dotenv()
|
| 21 |
|
| 22 |
-
# Obtém a chave da API do ambiente (no Hugging Face, configure como Secret)
|
| 23 |
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
|
| 24 |
if not GEMINI_API_KEY:
|
| 25 |
raise ValueError("❌ A chave GEMINI_API_KEY não foi encontrada. Configure-a como Secret no Hugging Face Spaces.")
|
| 26 |
|
| 27 |
-
# URL da API Gemini
|
| 28 |
GEMINI_API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={GEMINI_API_KEY}"
|
| 29 |
|
| 30 |
-
# Configurações da aplicação
|
| 31 |
CONFIG = {
|
| 32 |
"max_retry_attempts": 3,
|
| 33 |
-
"retry_delay": 10,
|
| 34 |
"timeout": 60,
|
| 35 |
"temperatura": 0.2,
|
| 36 |
"max_tokens": 4096
|
| 37 |
}
|
| 38 |
|
| 39 |
-
# Limite de 60 requisições por minuto (ajuste conforme seu limite real)
|
| 40 |
rate_limiter = RateLimiter(max_calls=60, period=60)
|
| 41 |
|
| 42 |
-
# Banco de dicas contextuais
|
| 43 |
DICAS_OAB = {
|
| 44 |
"Endereçamento": "Em relações de consumo, prefira o foro do domicílio do autor (art. 101, I, CDC).",
|
| 45 |
"Qualificação": "Mencione nome, estado civil (se informado), endereço (use 'Rua XXX' se não fornecido).",
|
|
@@ -48,7 +42,6 @@ DICAS_OAB = {
|
|
| 48 |
"Custas": "Inclua 'Requer a juntada da guia de custas devidamente recolhida'."
|
| 49 |
}
|
| 50 |
|
| 51 |
-
# Histórico de correções e cache
|
| 52 |
HISTORICO = []
|
| 53 |
CORRECAO_CACHE = {}
|
| 54 |
|
|
@@ -223,6 +216,7 @@ def estruturar_prompt_correcao(caso, peticao, modo_treinamento=False):
|
|
| 223 |
3. Falta de pedido de juntada de custas resulta em perda de 1,0 ponto
|
| 224 |
4. Use marcadores para dados não fornecidos (ex: "Rua XXX", "CPF XXX")
|
| 225 |
5. Não penalize por informações ausentes no caso original
|
|
|
|
| 226 |
**Instruções Especiais para Endereçamento:**
|
| 227 |
- CPC (art. 46): foro competente é o domicílio do réu; CDC (art. 101, I): em relação de consumo, foro do autor é correto.
|
| 228 |
- Avalie o contexto: priorize o foro do autor em casos consumeristas.
|
|
@@ -267,17 +261,18 @@ def estruturar_prompt_correcao(caso, peticao, modo_treinamento=False):
|
|
| 267 |
if modo_treinamento:
|
| 268 |
base_prompt += f"""
|
| 269 |
**Modo de Treinamento Interativo Ativado:**
|
| 270 |
-
- Para cada item com pontuação
|
| 271 |
-
a) Uma explicação detalhada
|
| 272 |
-
b) Uma sugestão de redação corrigida baseada no caso.
|
| 273 |
-
c) Uma dica prática
|
|
|
|
| 274 |
"""
|
| 275 |
return base_prompt
|
| 276 |
|
| 277 |
def estruturar_prompt_geracao_caso():
|
| 278 |
return """
|
| 279 |
Crie um caso fictício detalhado para uma petição inicial cível, similar ao exemplo:
|
| 280 |
-
"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
|
| 281 |
O caso deve:
|
| 282 |
1. Conter uma situação clara de violação de direitos que justifique uma ação cível
|
| 283 |
2. Incluir valores específicos de danos materiais e/ou morais
|
|
@@ -346,7 +341,6 @@ def corrigir_peticao(caso, peticao_texto=None, peticao_arquivo=None, modo_treina
|
|
| 346 |
if not peticao_conteudo.strip():
|
| 347 |
return "⚠️ Forneça uma petição."
|
| 348 |
|
| 349 |
-
# Chave única para o cache
|
| 350 |
cache_key = f"{caso[:50]}|{peticao_conteudo[:50]}|{modo_treinamento}"
|
| 351 |
if cache_key in CORRECAO_CACHE:
|
| 352 |
return CORRECAO_CACHE[cache_key]
|
|
@@ -382,12 +376,12 @@ def corrigir_peticao(caso, peticao_texto=None, peticao_arquivo=None, modo_treina
|
|
| 382 |
analise = correção_completa.strip()
|
| 383 |
correção_formatada = formatar_correção(analise)
|
| 384 |
HISTORICO.append({
|
| 385 |
-
"data": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
| 386 |
"caso": caso,
|
| 387 |
"peticao": peticao_conteudo,
|
| 388 |
"correcao_completa": correção_completa
|
| 389 |
})
|
| 390 |
-
CORRECAO_CACHE[cache_key] = correção_formatada
|
| 391 |
return correção_formatada
|
| 392 |
except requests.Timeout:
|
| 393 |
if tentativa < CONFIG["max_retry_attempts"] - 1:
|
|
@@ -427,11 +421,10 @@ def extrair_peticao_corrigida(correcao_completa):
|
|
| 427 |
try:
|
| 428 |
if "### Petição Corrigida" in correcao_completa:
|
| 429 |
inicio = correcao_completa.index("### Petição Corrigida") + len("### Petição Corrigida")
|
| 430 |
-
peticao_texto =
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
return peticao_texto.strip()
|
| 435 |
return "⚠️ Petição corrigida não encontrada na resposta da API."
|
| 436 |
except Exception as e:
|
| 437 |
return f"❌ Erro ao extrair petição corrigida: {str(e)}"
|
|
@@ -525,32 +518,32 @@ def comparar_peticao_melhorada(caso, peticao_texto, correcao):
|
|
| 525 |
elif linha.startswith('? '):
|
| 526 |
continue
|
| 527 |
|
|
|
|
|
|
|
|
|
|
| 528 |
resumo_alteracoes = []
|
| 529 |
for secao, alteracoes in secoes.items():
|
| 530 |
if alteracoes:
|
| 531 |
-
resumo_alteracoes.append(f"### {secao}
|
| 532 |
for alt in alteracoes[:5]:
|
| 533 |
-
resumo_alteracoes.append(f"
|
| 534 |
if len(alteracoes) > 5:
|
| 535 |
-
resumo_alteracoes.append(f"- ... e mais {len(alteracoes) - 5} alterações")
|
| 536 |
-
|
| 537 |
-
total_removidas = len([linha for linha in resultado_diff if "linha-removida" in linha])
|
| 538 |
-
total_adicionadas = len([linha for linha in resultado_diff if "linha-adicionada" in linha])
|
| 539 |
|
| 540 |
resumo_estatistico = f"""
|
| 541 |
## 📊 Resumo das Alterações
|
| 542 |
-
-
|
| 543 |
-
-
|
| 544 |
-
- Diferença
|
| 545 |
|
| 546 |
## 🔍 Alterações por Seção
|
| 547 |
-
{
|
| 548 |
|
| 549 |
## 📝 Visualização Detalhada
|
| 550 |
<style>
|
| 551 |
-
.linha-igual {{ color: #000000; }}
|
| 552 |
-
.linha-removida {{ color: #ffffff; background-color: #b30000; }}
|
| 553 |
-
.linha-adicionada {{ color: #ffffff; background-color: #006600; }}
|
| 554 |
</style>
|
| 555 |
{"".join(resultado_diff)}
|
| 556 |
"""
|
|
@@ -599,7 +592,7 @@ def interface_gradio():
|
|
| 599 |
with gr.Row():
|
| 600 |
gr.Markdown("### 🧩 Gerar Caso Fictício")
|
| 601 |
caso_btn = gr.Button("🔄 Gerar Novo Caso Fictício", variant="secondary")
|
| 602 |
-
caso_input = gr.Textbox(label="📋 Caso Fornecido pela Banca", lines=8, placeholder="Insira o texto COMPLETO do caso...")
|
| 603 |
with gr.Tabs():
|
| 604 |
with gr.TabItem("✏️ Inserir Texto"):
|
| 605 |
peticao_texto = gr.Textbox(label="Texto da Petição do Aluno", lines=15, placeholder="Insira a petição completa...")
|
|
|
|
| 16 |
from datetime import datetime
|
| 17 |
from ratelimiter import RateLimiter
|
| 18 |
|
|
|
|
| 19 |
load_dotenv()
|
| 20 |
|
|
|
|
| 21 |
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
|
| 22 |
if not GEMINI_API_KEY:
|
| 23 |
raise ValueError("❌ A chave GEMINI_API_KEY não foi encontrada. Configure-a como Secret no Hugging Face Spaces.")
|
| 24 |
|
|
|
|
| 25 |
GEMINI_API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={GEMINI_API_KEY}"
|
| 26 |
|
|
|
|
| 27 |
CONFIG = {
|
| 28 |
"max_retry_attempts": 3,
|
| 29 |
+
"retry_delay": 10,
|
| 30 |
"timeout": 60,
|
| 31 |
"temperatura": 0.2,
|
| 32 |
"max_tokens": 4096
|
| 33 |
}
|
| 34 |
|
|
|
|
| 35 |
rate_limiter = RateLimiter(max_calls=60, period=60)
|
| 36 |
|
|
|
|
| 37 |
DICAS_OAB = {
|
| 38 |
"Endereçamento": "Em relações de consumo, prefira o foro do domicílio do autor (art. 101, I, CDC).",
|
| 39 |
"Qualificação": "Mencione nome, estado civil (se informado), endereço (use 'Rua XXX' se não fornecido).",
|
|
|
|
| 42 |
"Custas": "Inclua 'Requer a juntada da guia de custas devidamente recolhida'."
|
| 43 |
}
|
| 44 |
|
|
|
|
| 45 |
HISTORICO = []
|
| 46 |
CORRECAO_CACHE = {}
|
| 47 |
|
|
|
|
| 216 |
3. Falta de pedido de juntada de custas resulta em perda de 1,0 ponto
|
| 217 |
4. Use marcadores para dados não fornecidos (ex: "Rua XXX", "CPF XXX")
|
| 218 |
5. Não penalize por informações ausentes no caso original
|
| 219 |
+
6. A frase "Nestes termos, pede deferimento" com vírgula é incorreta; corrija para "Nestes termos pede deferimento" e destaque o erro na análise.
|
| 220 |
**Instruções Especiais para Endereçamento:**
|
| 221 |
- CPC (art. 46): foro competente é o domicílio do réu; CDC (art. 101, I): em relação de consumo, foro do autor é correto.
|
| 222 |
- Avalie o contexto: priorize o foro do autor em casos consumeristas.
|
|
|
|
| 261 |
if modo_treinamento:
|
| 262 |
base_prompt += f"""
|
| 263 |
**Modo de Treinamento Interativo Ativado:**
|
| 264 |
+
- Para cada item com pontuação inferior ao máximo, forneça:
|
| 265 |
+
a) Uma explicação detalhada (mínimo 2 frases) descrevendo o erro e por que ele está errado conforme o CPC ou CDC.
|
| 266 |
+
b) Uma sugestão de redação corrigida baseada exclusivamente no caso fornecido.
|
| 267 |
+
c) Uma dica prática extraída do banco de dicas ({json.dumps(DICAS_OAB)}) ou uma dica personalizada se o banco não se aplicar.
|
| 268 |
+
- Não resuma; detalhe cada erro de forma pedagógica e clara.
|
| 269 |
"""
|
| 270 |
return base_prompt
|
| 271 |
|
| 272 |
def estruturar_prompt_geracao_caso():
|
| 273 |
return """
|
| 274 |
Crie um caso fictício detalhado para uma petição inicial cível, similar ao exemplo:
|
| 275 |
+
"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."
|
| 276 |
O caso deve:
|
| 277 |
1. Conter uma situação clara de violação de direitos que justifique uma ação cível
|
| 278 |
2. Incluir valores específicos de danos materiais e/ou morais
|
|
|
|
| 341 |
if not peticao_conteudo.strip():
|
| 342 |
return "⚠️ Forneça uma petição."
|
| 343 |
|
|
|
|
| 344 |
cache_key = f"{caso[:50]}|{peticao_conteudo[:50]}|{modo_treinamento}"
|
| 345 |
if cache_key in CORRECAO_CACHE:
|
| 346 |
return CORRECAO_CACHE[cache_key]
|
|
|
|
| 376 |
analise = correção_completa.strip()
|
| 377 |
correção_formatada = formatar_correção(analise)
|
| 378 |
HISTORICO.append({
|
| 379 |
+
"data": datetime.now().strftime("%Y-%-m-%d %H:%M:%S"),
|
| 380 |
"caso": caso,
|
| 381 |
"peticao": peticao_conteudo,
|
| 382 |
"correcao_completa": correção_completa
|
| 383 |
})
|
| 384 |
+
CORRECAO_CACHE[cache_key] = correção_formatada
|
| 385 |
return correção_formatada
|
| 386 |
except requests.Timeout:
|
| 387 |
if tentativa < CONFIG["max_retry_attempts"] - 1:
|
|
|
|
| 421 |
try:
|
| 422 |
if "### Petição Corrigida" in correcao_completa:
|
| 423 |
inicio = correcao_completa.index("### Petição Corrigida") + len("### Petição Corrigida")
|
| 424 |
+
peticao_texto = correcao_completa[inicio:].strip()
|
| 425 |
+
fim = peticao_texto.find("###") if "###" in peticao_texto else len(peticao_texto)
|
| 426 |
+
peticao_texto = peticao_texto[:fim].strip()
|
| 427 |
+
return peticao_texto
|
|
|
|
| 428 |
return "⚠️ Petição corrigida não encontrada na resposta da API."
|
| 429 |
except Exception as e:
|
| 430 |
return f"❌ Erro ao extrair petição corrigida: {str(e)}"
|
|
|
|
| 518 |
elif linha.startswith('? '):
|
| 519 |
continue
|
| 520 |
|
| 521 |
+
total_removidas = len([linha for linha in resultado_diff if "linha-removida" in linha])
|
| 522 |
+
total_adicionadas = len([linha for linha in resultado_diff if "linha-adicionada" in linha])
|
| 523 |
+
|
| 524 |
resumo_alteracoes = []
|
| 525 |
for secao, alteracoes in secoes.items():
|
| 526 |
if alteracoes:
|
| 527 |
+
resumo_alteracoes.append(f"### {secao}\n")
|
| 528 |
for alt in alteracoes[:5]:
|
| 529 |
+
resumo_alteracoes.append(f" {alt}")
|
| 530 |
if len(alteracoes) > 5:
|
| 531 |
+
resumo_alteracoes.append(f" - ... e mais {len(alteracoes) - 5} alterações\n")
|
|
|
|
|
|
|
|
|
|
| 532 |
|
| 533 |
resumo_estatistico = f"""
|
| 534 |
## 📊 Resumo das Alterações
|
| 535 |
+
- **Linhas Removidas:** {total_removidas}
|
| 536 |
+
- **Linhas Adicionadas:** {total_adicionadas}
|
| 537 |
+
- **Diferença Líquida:** {total_adicionadas - total_removidas} linhas
|
| 538 |
|
| 539 |
## 🔍 Alterações por Seção
|
| 540 |
+
{''.join(resumo_alteracoes)}
|
| 541 |
|
| 542 |
## 📝 Visualização Detalhada
|
| 543 |
<style>
|
| 544 |
+
.linha-igual {{ color: #000000; margin: 2px 0; }}
|
| 545 |
+
.linha-removida {{ color: #ffffff; background-color: #b30000; margin: 2px 0; padding: 2px; }}
|
| 546 |
+
.linha-adicionada {{ color: #ffffff; background-color: #006600; margin: 2px 0; padding: 2px; }}
|
| 547 |
</style>
|
| 548 |
{"".join(resultado_diff)}
|
| 549 |
"""
|
|
|
|
| 592 |
with gr.Row():
|
| 593 |
gr.Markdown("### 🧩 Gerar Caso Fictício")
|
| 594 |
caso_btn = gr.Button("🔄 Gerar Novo Caso Fictício", variant="secondary")
|
| 595 |
+
caso_input = gr.Textbox(label="� - 📋 Caso Fornecido pela Banca", lines=8, placeholder="Insira o texto COMPLETO do caso...")
|
| 596 |
with gr.Tabs():
|
| 597 |
with gr.TabItem("✏️ Inserir Texto"):
|
| 598 |
peticao_texto = gr.Textbox(label="Texto da Petição do Aluno", lines=15, placeholder="Insira a petição completa...")
|