GabrielGD's picture
Update app.py
04077d0 verified
Raw
History Blame Contribute Delete
8.65 kB
import gradio as gr
import os
import json
import logging
from dotenv import load_dotenv
import google.generativeai as genai
from fpdf import FPDF
from datetime import datetime
import time
import re
# Configuração de Logs
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# Carregar API Key
load_dotenv()
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
if GEMINI_API_KEY:
genai.configure(api_key=GEMINI_API_KEY)
# --- CONFIGURAÇÃO DA IA ---
# Usar Flash é o mais rápido e barato
MODEL_NAME = "gemini-3-flash-preview"
GENERATION_CONFIG = {
"temperature": 0.4, # Temperatura mais baixa = menos alucinação e JSON mais estável
"max_output_tokens": 15000,
"response_mime_type": "application/json"
}
# --- MATRIZ DE MATÉRIAS ---
# Blocos mantidos, mas a estratégia de chamada muda
BLOCOS_PROVA = {
1: [("Ética Profissional", 8), ("Filosofia", 2), ("Constitucional", 7), ("Humanos", 2), ("Internacional", 1)],
2: [("Tributário", 5), ("Administrativo", 6), ("Ambiental", 2), ("Civil", 6), ("ECA", 2)],
3: [("Consumidor", 2), ("Empresarial", 5), ("Processo Civil", 7), ("Penal", 6)],
4: [("Processo Penal", 6), ("Trabalho", 6), ("Processo do Trabalho", 5), ("Previdenciário", 2)]
}
# --- PROMPTS OTIMIZADOS ---
def get_prompt_bloco(numero_bloco, lista_materias):
materias_str = ", ".join([f"{qtd} de {nome}" for nome, qtd in lista_materias])
return f"""
Você é a BANCA FGV (OAB 1ª FASE). Gere o BLOCO {numero_bloco}/4 da prova.
ITENS A GERAR (Total 20): {materias_str}.
REGRAS RÍGIDAS DE FORMATAÇÃO JSON:
1. Retorne APENAS UM JSON VÁLIDO (Lista de Objetos).
2. NÃO use Markdown (sem ```json no inicio).
3. Enunciados longos (estilo caso concreto).
MODELO DE RESPOSTA:
[
{{
"materia": "Nome da Matéria",
"enunciado": "João da Silva, residente em...",
"alternativas": {{"A": "Opção A", "B": "Opção B", "C": "Opção C", "D": "Opção D"}},
"correta": "A",
"justificativa": "Conforme Art. X da Lei Y..."
}}
]
"""
# --- PARSER ROBUSTO ---
def extrair_json_seguro(texto):
"""Tenta limpar e extrair JSON mesmo se a IA mandar lixo junto"""
try:
# Limpeza agressiva
texto = texto.strip()
texto = re.sub(r'^```json', '', texto, flags=re.MULTILINE)
texto = re.sub(r'^```', '', texto, flags=re.MULTILINE)
# Tenta parse direto
return json.loads(texto)
except:
# Tenta achar a lista []
try:
inicio = texto.find('[')
fim = texto.rfind(']') + 1
if inicio != -1 and fim != -1:
return json.loads(texto[inicio:fim])
except Exception as e:
return []
return []
# --- PDF ENGINE ---
class ProvaOAB_PDF(FPDF):
def header(self):
self.set_font('Arial', 'B', 12)
self.cell(0, 10, 'SIMULADO OAB 1a FASE - PADRAO FGV', 0, 1, 'C')
self.ln(5)
def footer(self):
self.set_y(-15)
self.set_font('Arial', 'I', 8)
self.cell(0, 10, f'Pagina {self.page_no()}', 0, 0, 'C')
def safe_text(text):
if not text: return ""
text = str(text).replace('–', '-').replace('“', '"').replace('”', '"').replace("’", "'")
return text.encode('latin-1', 'replace').decode('latin-1')
def gerar_pdf_prova(questoes):
pdf = ProvaOAB_PDF()
pdf.set_auto_page_break(auto=True, margin=15)
pdf.add_page()
pdf.set_font('Arial', '', 10)
pdf.multi_cell(0, 5, "CADERNO DE QUESTOES.\nTempo de prova: 5 horas.")
pdf.ln(10)
num = 1
for q in questoes:
pdf.set_font('Arial', 'B', 11)
pdf.set_fill_color(240, 240, 240)
titulo = safe_text(f"Questao {num} - {q.get('materia', 'Geral')}")
pdf.cell(0, 8, titulo, 0, 1, 'L', 1)
pdf.ln(2)
pdf.set_font('Arial', '', 10)
enunciado = safe_text(q.get('enunciado', '...'))
pdf.multi_cell(0, 5, enunciado)
pdf.ln(3)
alts = q.get('alternativas', {})
for letra in ['A', 'B', 'C', 'D']:
texto_alt = safe_text(f"({letra}) {alts.get(letra, '...')}")
pdf.multi_cell(0, 5, texto_alt)
pdf.ln(1)
pdf.ln(5)
num += 1
filename = f"Prova_OAB_{datetime.now().strftime('%H%M')}.pdf"
pdf.output(filename)
return filename
def gerar_pdf_gabarito(questoes):
pdf = ProvaOAB_PDF()
pdf.add_page()
pdf.set_font('Arial', 'B', 14)
pdf.cell(0, 10, 'GABARITO COMENTADO', 0, 1, 'C')
pdf.ln(10)
num = 1
for q in questoes:
pdf.set_font('Arial', 'B', 10)
header = safe_text(f"{num}) Gabarito: {q.get('correta', '?')}")
pdf.cell(0, 6, header, 0, 1)
pdf.set_font('Arial', '', 10)
just = safe_text(f"Justificativa: {q.get('justificativa', 'Sem comentario.')}")
pdf.multi_cell(0, 5, just)
pdf.ln(3)
num += 1
filename = f"Gabarito_OAB_{datetime.now().strftime('%H%M')}.pdf"
pdf.output(filename)
return filename
# --- LOOP COM CONTROLE DE RATE LIMIT ---
def gerar_simulado_completo(progress=gr.Progress()):
todas_questoes = []
log_erros = ""
if not GEMINI_API_KEY:
return None, None, "❌ ERRO: API Key não configurada."
model = genai.GenerativeModel(model_name=MODEL_NAME, generation_config=GENERATION_CONFIG)
# Processar 4 Blocos
for i in range(1, 5):
progress((i-1)/4, f"Gerando Bloco {i}/4... (Aguardando resfriamento da API)")
# === ESTRATÉGIA ANTI-429 (RATE LIMIT) ===
# Se não for o primeiro bloco, espera 20 segundos OBRIGATORIAMENTE para a cota resetar
if i > 1:
time.sleep(20)
materias = BLOCOS_PROVA[i]
prompt = get_prompt_bloco(i, materias)
sucesso_bloco = False
tentativas = 0
while not sucesso_bloco and tentativas < 3:
try:
response = model.generate_content(prompt)
questoes = extrair_json_seguro(response.text)
if questoes and isinstance(questoes, list) and len(questoes) > 0:
todas_questoes.extend(questoes)
log_erros += f"✅ Bloco {i}: {len(questoes)} questões geradas.\n"
sucesso_bloco = True
else:
raise ValueError("JSON vazio ou inválido")
except Exception as e:
erro_str = str(e)
log_erros += f"⚠️ Erro Bloco {i} (Tentativa {tentativas+1}): {erro_str[:100]}...\n"
tentativas += 1
# Se for erro de cota (429), espera mais tempo (30s)
if "429" in erro_str or "quota" in erro_str.lower():
log_erros += "⏳ Cota excedida. Esperando 30 segundos...\n"
time.sleep(30)
else:
time.sleep(5) # Erro genérico, espera curta
if not sucesso_bloco:
log_erros += f"‼️ FALHA CRÍTICA NO BLOCO {i}. Pulando...\n"
progress(0.9, "Gerando PDFs...")
if len(todas_questoes) == 0:
return None, None, f"FALHA TOTAL:\n{log_erros}"
try:
file_prova = gerar_pdf_prova(todas_questoes)
file_gabarito = gerar_pdf_gabarito(todas_questoes)
return file_prova, file_gabarito, f"SUCESSO!\nTotal: {len(todas_questoes)} questões.\n\nLog:\n{log_erros}"
except Exception as e:
return None, None, f"Erro PDF: {str(e)}\nLog:\n{log_erros}"
# --- INTERFACE ---
def interface():
with gr.Blocks(theme=gr.themes.Soft(), title="JusTutor 1ª Fase") as demo:
gr.Markdown("# 🏛️ JusTutor: Simulador OAB 1ª Fase (Modo Econômico)")
gr.Markdown("Gerador otimizado para evitar erros de cota (429). O processo é mais lento propositalmente.")
with gr.Row():
btn_gerar = gr.Button("🚀 Gerar Simulado (Pode levar 3 a 4 min)", variant="primary")
with gr.Row():
status = gr.Textbox(label="Log Detalhado", lines=10)
with gr.Row():
down_prova = gr.File(label="📄 Baixar Prova")
down_gabarito = gr.File(label="📄 Baixar Gabarito")
btn_gerar.click(gerar_simulado_completo, inputs=[], outputs=[down_prova, down_gabarito, status])
return demo
if __name__ == "__main__":
app = interface()
app.launch()