File size: 8,647 Bytes
b9a9177
 
 
 
 
 
 
 
 
3cded32
b9a9177
 
 
 
 
 
 
 
 
 
 
 
 
04077d0
709eead
b9a9177
 
04077d0
 
b9a9177
 
 
3cded32
04077d0
b9a9177
3cded32
 
 
 
b9a9177
 
04077d0
b9a9177
3cded32
b9a9177
3cded32
 
b9a9177
04077d0
 
 
 
b9a9177
04077d0
b9a9177
3cded32
04077d0
 
 
3cded32
04077d0
3cded32
b9a9177
 
 
04077d0
3cded32
04077d0
3cded32
04077d0
 
 
 
 
 
3cded32
 
04077d0
 
 
 
 
 
 
 
3cded32
 
 
b9a9177
 
 
3cded32
 
b9a9177
 
 
3cded32
b9a9177
3cded32
 
04077d0
3cded32
b9a9177
3cded32
b9a9177
 
 
 
3cded32
 
b9a9177
3cded32
 
 
 
 
 
 
 
 
 
 
 
b9a9177
3cded32
 
 
 
 
b9a9177
3cded32
 
 
 
b9a9177
 
 
3cded32
b9a9177
 
3cded32
b9a9177
 
 
3cded32
 
b9a9177
3cded32
 
b9a9177
 
3cded32
 
 
 
 
b9a9177
 
 
 
04077d0
b9a9177
 
 
3cded32
b9a9177
 
04077d0
b9a9177
 
 
3cded32
 
04077d0
 
 
 
 
 
 
3cded32
 
b9a9177
3cded32
 
 
 
 
 
 
 
 
 
 
 
 
04077d0
 
3cded32
04077d0
 
3cded32
04077d0
 
 
 
 
 
 
b9a9177
3cded32
 
b9a9177
3cded32
 
 
04077d0
3cded32
 
 
 
04077d0
b9a9177
04077d0
b9a9177
 
 
 
04077d0
 
b9a9177
 
04077d0
b9a9177
 
04077d0
b9a9177
 
3cded32
 
b9a9177
3cded32
b9a9177
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
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()