GabrielGD commited on
Commit
854e1b3
·
verified ·
1 Parent(s): e5c2ad4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +73 -123
app.py CHANGED
@@ -44,17 +44,21 @@ PECAS_CONSTITUCIONAL = [
44
  "Recurso Ordinário (ROC)", "Recurso Extraordinário (RE)", "Reclamação"
45
  ]
46
 
 
 
 
 
 
 
47
  # --- FUNÇÕES UTILITÁRIAS ---
48
 
49
  def limpar_json(texto_bruto):
50
  if not texto_bruto: return {}
51
- # Remove blocos markdown ```json
52
  texto = re.sub(r'^```json', '', texto_bruto, flags=re.MULTILINE)
53
  texto = re.sub(r'^```', '', texto, flags=re.MULTILINE)
54
  try:
55
  return json.loads(texto)
56
  except:
57
- # Tenta encontrar o primeiro objeto JSON válido se houver texto em volta
58
  match = re.search(r'\{.*\}', texto, re.DOTALL)
59
  if match: return json.loads(match.group(0))
60
  return {}
@@ -63,98 +67,75 @@ def contar_palavras(texto):
63
  if not texto: return "0 palavras"
64
  return f"{len(texto.split())} palavras"
65
 
66
- # --- PDF ENGINE (COM TABELAS) ---
67
 
68
  class JusTutorPDF(FPDF):
69
  def header(self):
70
- self.set_font('Helvetica', 'B', 14)
71
  self.set_text_color(50, 50, 50)
72
  self.cell(0, 10, 'JusTutor Pro - Simulado OAB 2a Fase', 0, 1, 'C')
73
  self.ln(5)
74
 
75
  def footer(self):
76
  self.set_y(-15)
77
- self.set_font('Helvetica', 'I', 8)
78
  self.cell(0, 10, f'Pagina {self.page_no()}', 0, 0, 'C')
79
 
80
  def safe_text(text):
81
- """Trata caracteres para evitar erros no FPDF"""
82
  if not text: return ""
83
  if not isinstance(text, str): text = str(text)
84
- # Substituições básicas de tipografia
85
  replacements = {'–': '-', '—': '-', '“': '"', '”': '"', '’': "'", '‘': "'", '…': '...'}
86
  for k, v in replacements.items(): text = text.replace(k, v)
87
- # Codificação para Latin-1 (padrão FPDF)
88
  return text.encode('latin-1', 'replace').decode('latin-1')
89
 
90
  def draw_table_row(pdf, item, valor, nota, motivo):
91
- """Desenha uma linha da tabela estilo FGV"""
92
- pdf.set_font("Helvetica", "", 10)
93
-
94
  line_height = 5
95
  x_start = pdf.get_x()
96
  y_start = pdf.get_y()
97
 
98
- # Calcular alturas
99
  pdf.multi_cell(90, line_height, safe_text(item), split_only=True)
100
  h_item = len(pdf.multi_cell(90, line_height, safe_text(item), split_only=True)) * line_height
101
-
102
  pdf.multi_cell(60, line_height, safe_text(motivo), split_only=True)
103
  h_motivo = len(pdf.multi_cell(60, line_height, safe_text(motivo), split_only=True)) * line_height
104
-
105
  row_height = max(h_item, h_motivo, 10)
106
 
107
- # Quebra de página se necessário
108
  if y_start + row_height > 270:
109
  pdf.add_page()
110
  y_start = pdf.get_y()
111
 
112
- # Col 1: Item
113
  pdf.set_xy(x_start, y_start)
114
  pdf.multi_cell(90, line_height, safe_text(item), border=1)
115
-
116
- # Col 2: Valor
117
  pdf.set_xy(x_start + 90, y_start)
118
  pdf.cell(20, row_height, safe_text(str(valor)), border=1, align='C')
119
-
120
- # Col 3: Nota
121
  pdf.set_xy(x_start + 110, y_start)
122
  pdf.cell(20, row_height, safe_text(str(nota)), border=1, align='C')
123
-
124
- # Col 4: Motivo
125
  pdf.set_xy(x_start + 130, y_start)
126
- pdf.set_font("Helvetica", "I", 9)
127
  pdf.multi_cell(60, line_height, safe_text(motivo), border=1)
128
-
129
  pdf.set_y(y_start + row_height)
130
 
131
  def gerar_pdf_caderno(area, enc_peca, q1, q2, q3, q4):
132
- """Gera o caderno de questões"""
133
  pdf = JusTutorPDF()
134
  pdf.set_auto_page_break(auto=True, margin=15)
135
  pdf.add_page()
136
-
137
- pdf.set_font("Helvetica", "B", 12)
138
  pdf.cell(0, 10, safe_text(f"CADERNO DE QUESTOES - {area}"), 0, 1, 'C')
139
  pdf.cell(0, 10, safe_text(f"Gerado em: {datetime.now().strftime('%d/%m/%Y')}"), 0, 1, 'C')
140
  pdf.ln(10)
141
 
142
  def add_q(title, body):
143
  if not body: return
144
- pdf.set_font("Helvetica", "B", 12)
145
  pdf.set_fill_color(230, 230, 230)
146
  pdf.cell(0, 10, safe_text(title), 0, 1, 'L', fill=True)
147
  pdf.ln(2)
148
- pdf.set_font("Helvetica", "", 11)
149
  pdf.multi_cell(0, 6, safe_text(body))
150
  pdf.ln(8)
151
 
152
  add_q("PECA PRATICO-PROFISSIONAL", enc_peca)
153
- add_q("QUESTAO 1", q1)
154
- add_q("QUESTAO 2", q2)
155
- add_q("QUESTAO 3", q3)
156
- add_q("QUESTAO 4", q4)
157
-
158
  filename = f"Caderno_Prova_{datetime.now().strftime('%H%M%S')}.pdf"
159
  pdf.output(filename)
160
  return filename
@@ -164,33 +145,28 @@ def gerar_pdf_completo(area, enc_peca, q1, q2, q3, q4, r_peca, r_q1, r_q2, r_q3,
164
  pdf.set_auto_page_break(auto=True, margin=15)
165
  pdf.add_page()
166
 
167
- # 1. Respostas
168
- pdf.set_font("Helvetica", "B", 14)
169
  pdf.cell(0, 10, safe_text("FOLHA DE RESPOSTAS"), 0, 1, 'C')
170
  pdf.ln(5)
171
 
172
  def add_resp(title, content):
173
  if not content: content = "[Em branco]"
174
- pdf.set_font("Helvetica", "B", 11)
175
  pdf.cell(0, 8, safe_text(title), 0, 1)
176
  pdf.set_font("Courier", "", 10)
177
  pdf.multi_cell(0, 5, safe_text(content), border=1)
178
  pdf.ln(5)
179
 
180
- add_resp("Sua Peca", r_peca)
181
- add_resp("Questao 1", r_q1)
182
- add_resp("Questao 2", r_q2)
183
- add_resp("Questao 3", r_q3)
184
- add_resp("Questao 4", r_q4)
185
 
186
- # 2. Espelho (Tabela)
187
  pdf.add_page()
188
- pdf.set_font("Helvetica", "B", 14)
189
- pdf.cell(0, 10, safe_text("ESPELHO DE CORRECAO (PADRAO FGV)"), 0, 1, 'C')
190
  pdf.ln(5)
191
-
192
  pdf.set_fill_color(200, 200, 200)
193
- pdf.set_font("Helvetica", "B", 10)
194
  pdf.cell(90, 8, "ITEM AVALIADO", 1, 0, 'C', True)
195
  pdf.cell(20, 8, "VALOR", 1, 0, 'C', True)
196
  pdf.cell(20, 8, "NOTA", 1, 0, 'C', True)
@@ -198,35 +174,25 @@ def gerar_pdf_completo(area, enc_peca, q1, q2, q3, q4, r_peca, r_q1, r_q2, r_q3,
198
 
199
  try:
200
  if isinstance(dados_correcao, str):
201
- pdf.set_font("Helvetica", "", 10)
202
- pdf.multi_cell(0, 5, safe_text(dados_correcao))
203
  else:
204
  tabela = dados_correcao.get("tabela_notas", [])
205
  nota_final = dados_correcao.get("nota_final", "0.0")
206
-
207
  for linha in tabela:
208
  draw_table_row(pdf, linha.get("item", ""), linha.get("valor", ""), linha.get("nota_aluno", ""), linha.get("motivo", ""))
209
-
210
  pdf.ln(5)
211
- pdf.set_font("Helvetica", "B", 12)
212
  pdf.cell(0, 10, safe_text(f"NOTA FINAL: {nota_final} / 10.0"), 0, 1, 'R')
213
-
214
  except Exception as e:
215
- pdf.multi_cell(0, 5, safe_text(f"Erro ao gerar tabela: {str(e)}"))
216
 
217
- # 3. Gabarito
218
  if modelo:
219
- pdf.add_page()
220
- pdf.set_font("Helvetica", "B", 14); pdf.cell(0, 10, safe_text("GABARITO MODELO"), 0, 1, 'C')
221
- pdf.set_font("Helvetica", "", 10)
222
- pdf.multi_cell(0, 5, safe_text(str(modelo).replace("**", "")))
223
 
224
- # 4. Mentor
225
  if mentor:
226
- pdf.add_page()
227
- pdf.set_font("Helvetica", "B", 14); pdf.cell(0, 10, safe_text("MENTOR VADE MECUM"), 0, 1, 'C')
228
- pdf.set_font("Helvetica", "", 10)
229
- pdf.multi_cell(0, 5, safe_text(str(mentor).replace("**", "")))
230
 
231
  filename = f"Relatorio_Final_{datetime.now().strftime('%H%M%S')}.pdf"
232
  pdf.output(filename)
@@ -236,6 +202,16 @@ def gerar_pdf_completo(area, enc_peca, q1, q2, q3, q4, r_peca, r_q1, r_q2, r_q3,
236
 
237
  def get_system_instruction(area, modo="geracao"):
238
  base = f"Você é a BANCA EXAMINADORA FGV (OAB 2ª FASE - {area}). "
 
 
 
 
 
 
 
 
 
 
239
  if modo == "geracao":
240
  return base + """
241
  Crie PROVAS DIFÍCEIS e INÉDITAS.
@@ -244,29 +220,23 @@ def get_system_instruction(area, modo="geracao"):
244
  """
245
  else:
246
  return base + """
247
- CORRIJA COM RIGOR ABSOLUTO.
248
- IMPORTANTE: O Output "tabela_notas" deve ser uma LISTA DE OBJETOS JSON.
249
- Estrutura JSON Obrigatória:
250
  {
251
- "tabela_notas": [
252
- {"item": "Endereçamento", "valor": "0.10", "nota_aluno": "0.10", "motivo": "Correto"},
253
- {"item": "Q1 - Letra A", "valor": "0.65", "nota_aluno": "0.00", "motivo": "Não citou Artigo"}
254
- ],
255
- "nota_final": "Total",
256
- "gabarito_modelo": "Texto...",
257
- "mentor_vade_mecum": "Texto..."
258
  }
259
  """
260
 
261
  def get_prompt_gerador(area, peca_alvo):
262
  instrucao = f"Peça obrigatória: {peca_alvo}" if "Aleatória" not in peca_alvo else "Escolha peça aleatória."
263
- return f"Gere simulado de {area}. {instrucao}. Inclua datas, valores e pegadinhas."
 
264
 
265
  def get_prompt_correcao(area, gabarito, respostas):
266
  return f"""
267
- Corrija a prova.
268
  GABARITO: {json.dumps(gabarito, ensure_ascii=False)}
269
- RESPOSTAS: {json.dumps(respostas, ensure_ascii=False)}
270
  """
271
 
272
  # --- BACKEND ---
@@ -279,22 +249,18 @@ def gerar_simulado(area, peca_treino):
279
  try:
280
  response = model.generate_content(get_prompt_gerador(area, peca_treino))
281
  dados = limpar_json(response.text)
282
-
283
  pdf_caderno = gerar_pdf_caderno(area, dados.get("peca_enunciado"), dados.get("q1_enunciado"), dados.get("q2_enunciado"), dados.get("q3_enunciado"), dados.get("q4_enunciado"))
284
 
285
- # AQUI FOI A CORREÇÃO: RETORNAR EXATAMENTE 16 ITENS PARA O GRADIO
286
  return (
287
- dados.get("gabarito_oculto"), # 1
288
- dados.get("peca_enunciado"), dados.get("peca_enunciado"), # 2, 3
289
- dados.get("q1_enunciado"), dados.get("q1_enunciado"), # 4, 5 (Duplicado para state/markdown)
290
- dados.get("q2_enunciado"), dados.get("q2_enunciado"), # 6, 7
291
- dados.get("q3_enunciado"), dados.get("q3_enunciado"), # 8, 9
292
- dados.get("q4_enunciado"), dados.get("q4_enunciado"), # 10, 11
293
- gr.update(visible=True), # 12
294
- gr.update(interactive=True), # 13
295
- pdf_caderno, # 14
296
- gr.update(visible=True), # 15
297
- "✅ Prova Gerada! Baixe o caderno." # 16
298
  )
299
  except Exception as e:
300
  logger.error(f"Erro: {e}")
@@ -302,34 +268,29 @@ def gerar_simulado(area, peca_treino):
302
 
303
  def corrigir_simulado(gabarito, area, r_peca, r_q1, r_q2, r_q3, r_q4, enc_peca, enc_q1, enc_q2, enc_q3, enc_q4):
304
  if not gabarito: return [None]*4 + ["⚠️ Gere a prova antes."]
305
-
306
  respostas = {"peca": r_peca, "q1": r_q1, "q2": r_q2, "q3": r_q3, "q4": r_q4}
307
  model = genai.GenerativeModel(model_name=MODEL_NAME, system_instruction=get_system_instruction(area, "correcao"), generation_config=GENERATION_CONFIG)
308
 
309
  try:
310
  response = model.generate_content(get_prompt_correcao(area, gabarito, respostas))
311
  dados = limpar_json(response.text)
312
-
313
  tabela_md = "| Item | Valor | Nota | Motivo |\n|---|---|---|---|\n"
314
  for l in dados.get("tabela_notas", []):
315
  tabela_md += f"| {l.get('item','')} | {l.get('valor','')} | {l.get('nota_aluno','')} | {l.get('motivo','')} |\n"
316
 
317
  pdf_final = gerar_pdf_completo(area, enc_peca, enc_q1, enc_q2, enc_q3, enc_q4, r_peca, r_q1, r_q2, r_q3, r_q4, dados, dados.get("gabarito_modelo"), dados.get("mentor_vade_mecum"))
318
-
319
- return (
320
- tabela_md,
321
- dados.get("gabarito_modelo", "Erro"),
322
- dados.get("mentor_vade_mecum", "Erro"),
323
- pdf_final,
324
- "✅ Correção Realizada!"
325
- )
326
  except Exception as e:
327
  return [f"Erro: {e}"]*3 + [None, "❌ Falha"]
328
 
329
  # --- ORÁCULO ---
330
  def analisar_pdf_historico(file_obj, area):
331
  if not file_obj: return None, "⚠️ Envie o PDF."
332
- keywords = {"Direito do Trabalho": {"Reclamação": ["reclamação"], "Contestação": ["contestação"], "RO": ["recurso ordinário"]}, "Direito Constitucional": {"ADI": ["ação direta"], "MS": ["mandado de segurança"], "Ação Popular": ["ação popular"]}}
 
 
 
 
333
  termos = keywords.get(area, {})
334
  contagem = {k: 0 for k in termos.keys()}
335
  try:
@@ -348,28 +309,23 @@ def analisar_pdf_historico(file_obj, area):
348
 
349
  def interface():
350
  with gr.Blocks(theme=gr.themes.Soft(), title="JusTutor Ultimate") as demo:
351
-
352
  gabarito_state = gr.State()
353
-
354
- gr.Markdown("# ⚖️ JusTutor Pro: Preparação OAB 2ª Fase")
355
 
356
  with gr.Tabs():
357
  with gr.TabItem("📝 Sala de Prova"):
358
  with gr.Row(variant="panel"):
359
- area_sel = gr.Dropdown(["Direito do Trabalho", "Direito Constitucional"], value="Direito do Trabalho", label="Área")
360
- peca_sel = gr.Dropdown(PECAS_TRABALHO, value="Aleatória (Simulado Real)", label="Modo")
361
  btn_gerar = gr.Button("🚀 Iniciar Prova", variant="primary")
362
  status = gr.Textbox(label="Status", value="Pronto.", interactive=False)
363
 
364
  with gr.Group(visible=False) as area_prova:
365
  gr.Markdown("---")
366
-
367
- # BOTÃO DOWNLOAD CADERNO
368
  with gr.Row():
369
  btn_down_caderno = gr.DownloadButton("📥 Baixar Caderno de Questões (PDF)", visible=False)
370
 
371
  with gr.Row():
372
- # Caderno
373
  with gr.Column(scale=1):
374
  gr.Markdown("### 📘 Caderno de Questões")
375
  with gr.Accordion("PEÇA (5.0)", open=True):
@@ -384,7 +340,6 @@ def interface():
384
  with gr.Accordion("Q4 (1.25)", open=False):
385
  enc_q4 = gr.Markdown(); enc_q4_st = gr.State()
386
 
387
- # Respostas
388
  with gr.Column(scale=1):
389
  gr.Markdown("### ✏️ Folha de Respostas")
390
  with gr.Tabs():
@@ -407,7 +362,7 @@ def interface():
407
  with gr.TabItem("📚 Gabarito Modelo"): res_modelo = gr.Markdown()
408
  with gr.TabItem("🧭 Mentor Vade Mecum"): res_mentor = gr.Markdown()
409
 
410
- btn_pdf_final = gr.DownloadButton("📄 Baixar Relatório Final (Correção + Tabela)", label="Baixar PDF Final")
411
 
412
  with gr.TabItem("🔮 Oráculo"):
413
  file_oracle = gr.File(label="PDF Provas Anteriores")
@@ -415,11 +370,13 @@ def interface():
415
  plot_oracle = gr.Plot()
416
  txt_oracle = gr.Markdown()
417
 
418
- # --- LOGICA ---
419
- def atualiza_lista(area): return gr.update(choices=PECAS_TRABALHO if area == "Direito do Trabalho" else PECAS_CONSTITUCIONAL)
 
 
 
420
  area_sel.change(atualiza_lista, area_sel, peca_sel)
421
 
422
- # Gerar Prova
423
  btn_gerar.click(
424
  fn=lambda: "⏳ Gerando...", outputs=status
425
  ).then(
@@ -427,18 +384,11 @@ def interface():
427
  inputs=[area_sel, peca_sel],
428
  outputs=[
429
  gabarito_state,
430
- enc_peca_st, enc_peca,
431
- enc_q1_st, enc_q1,
432
- enc_q2_st, enc_q2,
433
- enc_q3_st, enc_q3,
434
- enc_q4_st, enc_q4,
435
- area_prova, btn_corrigir,
436
- btn_down_caderno, btn_down_caderno, # Arquivo + Visibilidade
437
- status
438
  ]
439
  )
440
 
441
- # Corrigir
442
  btn_corrigir.click(
443
  fn=lambda: "⏳ Corrigindo...", outputs=status
444
  ).then(
 
44
  "Recurso Ordinário (ROC)", "Recurso Extraordinário (RE)", "Reclamação"
45
  ]
46
 
47
+ PECAS_PENAL = [
48
+ "Aleatória (Simulado Real)", "Relaxamento de Prisão em Flagrante", "Liberdade Provisória",
49
+ "Queixa-Crime", "Resposta à Acusação", "Memoriais", "Apelação",
50
+ "Recurso em Sentido Estrito (RESE)", "Agravo em Execução", "Revisão Criminal"
51
+ ]
52
+
53
  # --- FUNÇÕES UTILITÁRIAS ---
54
 
55
  def limpar_json(texto_bruto):
56
  if not texto_bruto: return {}
 
57
  texto = re.sub(r'^```json', '', texto_bruto, flags=re.MULTILINE)
58
  texto = re.sub(r'^```', '', texto, flags=re.MULTILINE)
59
  try:
60
  return json.loads(texto)
61
  except:
 
62
  match = re.search(r'\{.*\}', texto, re.DOTALL)
63
  if match: return json.loads(match.group(0))
64
  return {}
 
67
  if not texto: return "0 palavras"
68
  return f"{len(texto.split())} palavras"
69
 
70
+ # --- PDF ENGINE ---
71
 
72
  class JusTutorPDF(FPDF):
73
  def header(self):
74
+ self.set_font('Arial', 'B', 14)
75
  self.set_text_color(50, 50, 50)
76
  self.cell(0, 10, 'JusTutor Pro - Simulado OAB 2a Fase', 0, 1, 'C')
77
  self.ln(5)
78
 
79
  def footer(self):
80
  self.set_y(-15)
81
+ self.set_font('Arial', 'I', 8)
82
  self.cell(0, 10, f'Pagina {self.page_no()}', 0, 0, 'C')
83
 
84
  def safe_text(text):
 
85
  if not text: return ""
86
  if not isinstance(text, str): text = str(text)
 
87
  replacements = {'–': '-', '—': '-', '“': '"', '”': '"', '’': "'", '‘': "'", '…': '...'}
88
  for k, v in replacements.items(): text = text.replace(k, v)
 
89
  return text.encode('latin-1', 'replace').decode('latin-1')
90
 
91
  def draw_table_row(pdf, item, valor, nota, motivo):
92
+ pdf.set_font("Arial", "", 10)
 
 
93
  line_height = 5
94
  x_start = pdf.get_x()
95
  y_start = pdf.get_y()
96
 
 
97
  pdf.multi_cell(90, line_height, safe_text(item), split_only=True)
98
  h_item = len(pdf.multi_cell(90, line_height, safe_text(item), split_only=True)) * line_height
 
99
  pdf.multi_cell(60, line_height, safe_text(motivo), split_only=True)
100
  h_motivo = len(pdf.multi_cell(60, line_height, safe_text(motivo), split_only=True)) * line_height
 
101
  row_height = max(h_item, h_motivo, 10)
102
 
 
103
  if y_start + row_height > 270:
104
  pdf.add_page()
105
  y_start = pdf.get_y()
106
 
 
107
  pdf.set_xy(x_start, y_start)
108
  pdf.multi_cell(90, line_height, safe_text(item), border=1)
 
 
109
  pdf.set_xy(x_start + 90, y_start)
110
  pdf.cell(20, row_height, safe_text(str(valor)), border=1, align='C')
 
 
111
  pdf.set_xy(x_start + 110, y_start)
112
  pdf.cell(20, row_height, safe_text(str(nota)), border=1, align='C')
 
 
113
  pdf.set_xy(x_start + 130, y_start)
114
+ pdf.set_font("Arial", "I", 9)
115
  pdf.multi_cell(60, line_height, safe_text(motivo), border=1)
 
116
  pdf.set_y(y_start + row_height)
117
 
118
  def gerar_pdf_caderno(area, enc_peca, q1, q2, q3, q4):
 
119
  pdf = JusTutorPDF()
120
  pdf.set_auto_page_break(auto=True, margin=15)
121
  pdf.add_page()
122
+ pdf.set_font("Arial", "B", 12)
 
123
  pdf.cell(0, 10, safe_text(f"CADERNO DE QUESTOES - {area}"), 0, 1, 'C')
124
  pdf.cell(0, 10, safe_text(f"Gerado em: {datetime.now().strftime('%d/%m/%Y')}"), 0, 1, 'C')
125
  pdf.ln(10)
126
 
127
  def add_q(title, body):
128
  if not body: return
129
+ pdf.set_font("Arial", "B", 12)
130
  pdf.set_fill_color(230, 230, 230)
131
  pdf.cell(0, 10, safe_text(title), 0, 1, 'L', fill=True)
132
  pdf.ln(2)
133
+ pdf.set_font("Arial", "", 11)
134
  pdf.multi_cell(0, 6, safe_text(body))
135
  pdf.ln(8)
136
 
137
  add_q("PECA PRATICO-PROFISSIONAL", enc_peca)
138
+ add_q("QUESTAO 1", q1); add_q("QUESTAO 2", q2); add_q("QUESTAO 3", q3); add_q("QUESTAO 4", q4)
 
 
 
 
139
  filename = f"Caderno_Prova_{datetime.now().strftime('%H%M%S')}.pdf"
140
  pdf.output(filename)
141
  return filename
 
145
  pdf.set_auto_page_break(auto=True, margin=15)
146
  pdf.add_page()
147
 
148
+ # Respostas
149
+ pdf.set_font("Arial", "B", 14)
150
  pdf.cell(0, 10, safe_text("FOLHA DE RESPOSTAS"), 0, 1, 'C')
151
  pdf.ln(5)
152
 
153
  def add_resp(title, content):
154
  if not content: content = "[Em branco]"
155
+ pdf.set_font("Arial", "B", 11)
156
  pdf.cell(0, 8, safe_text(title), 0, 1)
157
  pdf.set_font("Courier", "", 10)
158
  pdf.multi_cell(0, 5, safe_text(content), border=1)
159
  pdf.ln(5)
160
 
161
+ add_resp("Sua Peca", r_peca); add_resp("Q1", r_q1); add_resp("Q2", r_q2); add_resp("Q3", r_q3); add_resp("Q4", r_q4)
 
 
 
 
162
 
163
+ # Tabela
164
  pdf.add_page()
165
+ pdf.set_font("Arial", "B", 14)
166
+ pdf.cell(0, 10, safe_text("ESPELHO DE CORRECAO"), 0, 1, 'C')
167
  pdf.ln(5)
 
168
  pdf.set_fill_color(200, 200, 200)
169
+ pdf.set_font("Arial", "B", 10)
170
  pdf.cell(90, 8, "ITEM AVALIADO", 1, 0, 'C', True)
171
  pdf.cell(20, 8, "VALOR", 1, 0, 'C', True)
172
  pdf.cell(20, 8, "NOTA", 1, 0, 'C', True)
 
174
 
175
  try:
176
  if isinstance(dados_correcao, str):
177
+ pdf.set_font("Arial", "", 10); pdf.multi_cell(0, 5, safe_text(dados_correcao))
 
178
  else:
179
  tabela = dados_correcao.get("tabela_notas", [])
180
  nota_final = dados_correcao.get("nota_final", "0.0")
 
181
  for linha in tabela:
182
  draw_table_row(pdf, linha.get("item", ""), linha.get("valor", ""), linha.get("nota_aluno", ""), linha.get("motivo", ""))
 
183
  pdf.ln(5)
184
+ pdf.set_font("Arial", "B", 12)
185
  pdf.cell(0, 10, safe_text(f"NOTA FINAL: {nota_final} / 10.0"), 0, 1, 'R')
 
186
  except Exception as e:
187
+ pdf.multi_cell(0, 5, safe_text(f"Erro tabela: {str(e)}"))
188
 
 
189
  if modelo:
190
+ pdf.add_page(); pdf.set_font("Arial", "B", 14); pdf.cell(0, 10, safe_text("GABARITO MODELO"), 0, 1, 'C')
191
+ pdf.set_font("Arial", "", 10); pdf.multi_cell(0, 5, safe_text(str(modelo).replace("**", "")))
 
 
192
 
 
193
  if mentor:
194
+ pdf.add_page(); pdf.set_font("Arial", "B", 14); pdf.cell(0, 10, safe_text("MENTOR VADE MECUM"), 0, 1, 'C')
195
+ pdf.set_font("Arial", "", 10); pdf.multi_cell(0, 5, safe_text(str(mentor).replace("**", "")))
 
 
196
 
197
  filename = f"Relatorio_Final_{datetime.now().strftime('%H%M%S')}.pdf"
198
  pdf.output(filename)
 
202
 
203
  def get_system_instruction(area, modo="geracao"):
204
  base = f"Você é a BANCA EXAMINADORA FGV (OAB 2ª FASE - {area}). "
205
+
206
+ # REGRAS ESPECÍFICAS DE PENAL
207
+ if area == "Direito Penal":
208
+ base += """
209
+ CRITÉRIOS DE PENAL:
210
+ 1. Identificação da Peça: Focar no MOMENTO PROCESSUAL (Inquérito, Denúncia, Sentença, Trânsito em Julgado, Execução).
211
+ 2. Estrutura: Endereçamento, Preliminares (Nulidade, Extinção da Punibilidade), Mérito (Absolvição, Desclassificação), Subsidiárias (Dosimetria, Regime, Substituição de Pena).
212
+ 3. Datar no último dia do prazo (Súmula 710 STF para Processual Penal).
213
+ """
214
+
215
  if modo == "geracao":
216
  return base + """
217
  Crie PROVAS DIFÍCEIS e INÉDITAS.
 
220
  """
221
  else:
222
  return base + """
223
+ CORRIJA COM RIGOR ABSOLUTO. Output JSON OBRIGATÓRIO:
 
 
224
  {
225
+ "tabela_notas": [{"item": "...", "valor": "...", "nota_aluno": "...", "motivo": "..."}],
226
+ "nota_final": "...", "gabarito_modelo": "...", "mentor_vade_mecum": "..."
 
 
 
 
 
227
  }
228
  """
229
 
230
  def get_prompt_gerador(area, peca_alvo):
231
  instrucao = f"Peça obrigatória: {peca_alvo}" if "Aleatória" not in peca_alvo else "Escolha peça aleatória."
232
+ extra_penal = "Para Penal: Defina crime, pena em abstrato e o exato momento processual (ex: intimado da sentença ontem)." if area == "Direito Penal" else ""
233
+ return f"Gere simulado de {area}. {instrucao}. Inclua datas, valores/penas e pegadinhas. {extra_penal}"
234
 
235
  def get_prompt_correcao(area, gabarito, respostas):
236
  return f"""
237
+ Corrija a prova comparando com o Gabarito.
238
  GABARITO: {json.dumps(gabarito, ensure_ascii=False)}
239
+ RESPOSTAS ALUNO: {json.dumps(respostas, ensure_ascii=False)}
240
  """
241
 
242
  # --- BACKEND ---
 
249
  try:
250
  response = model.generate_content(get_prompt_gerador(area, peca_treino))
251
  dados = limpar_json(response.text)
 
252
  pdf_caderno = gerar_pdf_caderno(area, dados.get("peca_enunciado"), dados.get("q1_enunciado"), dados.get("q2_enunciado"), dados.get("q3_enunciado"), dados.get("q4_enunciado"))
253
 
 
254
  return (
255
+ dados.get("gabarito_oculto"),
256
+ dados.get("peca_enunciado"), dados.get("peca_enunciado"),
257
+ dados.get("q1_enunciado"), dados.get("q1_enunciado"),
258
+ dados.get("q2_enunciado"), dados.get("q2_enunciado"),
259
+ dados.get("q3_enunciado"), dados.get("q3_enunciado"),
260
+ dados.get("q4_enunciado"), dados.get("q4_enunciado"),
261
+ gr.update(visible=True), gr.update(interactive=True),
262
+ pdf_caderno, gr.update(visible=True),
263
+ "✅ Prova Gerada! Baixe o caderno."
 
 
264
  )
265
  except Exception as e:
266
  logger.error(f"Erro: {e}")
 
268
 
269
  def corrigir_simulado(gabarito, area, r_peca, r_q1, r_q2, r_q3, r_q4, enc_peca, enc_q1, enc_q2, enc_q3, enc_q4):
270
  if not gabarito: return [None]*4 + ["⚠️ Gere a prova antes."]
 
271
  respostas = {"peca": r_peca, "q1": r_q1, "q2": r_q2, "q3": r_q3, "q4": r_q4}
272
  model = genai.GenerativeModel(model_name=MODEL_NAME, system_instruction=get_system_instruction(area, "correcao"), generation_config=GENERATION_CONFIG)
273
 
274
  try:
275
  response = model.generate_content(get_prompt_correcao(area, gabarito, respostas))
276
  dados = limpar_json(response.text)
 
277
  tabela_md = "| Item | Valor | Nota | Motivo |\n|---|---|---|---|\n"
278
  for l in dados.get("tabela_notas", []):
279
  tabela_md += f"| {l.get('item','')} | {l.get('valor','')} | {l.get('nota_aluno','')} | {l.get('motivo','')} |\n"
280
 
281
  pdf_final = gerar_pdf_completo(area, enc_peca, enc_q1, enc_q2, enc_q3, enc_q4, r_peca, r_q1, r_q2, r_q3, r_q4, dados, dados.get("gabarito_modelo"), dados.get("mentor_vade_mecum"))
282
+ return (tabela_md, dados.get("gabarito_modelo", "Erro"), dados.get("mentor_vade_mecum", "Erro"), pdf_final, "✅ Correção Realizada!")
 
 
 
 
 
 
 
283
  except Exception as e:
284
  return [f"Erro: {e}"]*3 + [None, "❌ Falha"]
285
 
286
  # --- ORÁCULO ---
287
  def analisar_pdf_historico(file_obj, area):
288
  if not file_obj: return None, "⚠️ Envie o PDF."
289
+ keywords = {
290
+ "Direito do Trabalho": {"Reclamação": ["reclamação"], "Contestação": ["contestação"], "RO": ["recurso ordinário"]},
291
+ "Direito Constitucional": {"ADI": ["ação direta"], "MS": ["mandado de segurança"], "Ação Popular": ["ação popular"]},
292
+ "Direito Penal": {"Apelação": ["apelação", "sentença condenatória"], "RESE": ["recurso em sentido estrito", "pronúncia"], "Memoriais": ["memoriais", "audiência de instrução"], "Agravo": ["agravo em execução"]}
293
+ }
294
  termos = keywords.get(area, {})
295
  contagem = {k: 0 for k in termos.keys()}
296
  try:
 
309
 
310
  def interface():
311
  with gr.Blocks(theme=gr.themes.Soft(), title="JusTutor Ultimate") as demo:
 
312
  gabarito_state = gr.State()
313
+ gr.Markdown("# ⚖️ JusTutor Pro: Trabalho, Constitucional e Penal")
 
314
 
315
  with gr.Tabs():
316
  with gr.TabItem("📝 Sala de Prova"):
317
  with gr.Row(variant="panel"):
318
+ area_sel = gr.Dropdown(["Direito do Trabalho", "Direito Constitucional", "Direito Penal"], value="Direito Penal", label="Área")
319
+ peca_sel = gr.Dropdown(PECAS_PENAL, value="Aleatória (Simulado Real)", label="Modo")
320
  btn_gerar = gr.Button("🚀 Iniciar Prova", variant="primary")
321
  status = gr.Textbox(label="Status", value="Pronto.", interactive=False)
322
 
323
  with gr.Group(visible=False) as area_prova:
324
  gr.Markdown("---")
 
 
325
  with gr.Row():
326
  btn_down_caderno = gr.DownloadButton("📥 Baixar Caderno de Questões (PDF)", visible=False)
327
 
328
  with gr.Row():
 
329
  with gr.Column(scale=1):
330
  gr.Markdown("### 📘 Caderno de Questões")
331
  with gr.Accordion("PEÇA (5.0)", open=True):
 
340
  with gr.Accordion("Q4 (1.25)", open=False):
341
  enc_q4 = gr.Markdown(); enc_q4_st = gr.State()
342
 
 
343
  with gr.Column(scale=1):
344
  gr.Markdown("### ✏️ Folha de Respostas")
345
  with gr.Tabs():
 
362
  with gr.TabItem("📚 Gabarito Modelo"): res_modelo = gr.Markdown()
363
  with gr.TabItem("🧭 Mentor Vade Mecum"): res_mentor = gr.Markdown()
364
 
365
+ btn_pdf_final = gr.DownloadButton("📄 Baixar Relatório Final", label="Baixar PDF Final")
366
 
367
  with gr.TabItem("🔮 Oráculo"):
368
  file_oracle = gr.File(label="PDF Provas Anteriores")
 
370
  plot_oracle = gr.Plot()
371
  txt_oracle = gr.Markdown()
372
 
373
+ def atualiza_lista(area):
374
+ if area == "Direito do Trabalho": return gr.update(choices=PECAS_TRABALHO)
375
+ elif area == "Direito Constitucional": return gr.update(choices=PECAS_CONSTITUCIONAL)
376
+ else: return gr.update(choices=PECAS_PENAL)
377
+
378
  area_sel.change(atualiza_lista, area_sel, peca_sel)
379
 
 
380
  btn_gerar.click(
381
  fn=lambda: "⏳ Gerando...", outputs=status
382
  ).then(
 
384
  inputs=[area_sel, peca_sel],
385
  outputs=[
386
  gabarito_state,
387
+ enc_peca_st, enc_peca, enc_q1_st, enc_q1, enc_q2_st, enc_q2, enc_q3_st, enc_q3, enc_q4_st, enc_q4,
388
+ area_prova, btn_corrigir, btn_down_caderno, btn_down_caderno, status
 
 
 
 
 
 
389
  ]
390
  )
391
 
 
392
  btn_corrigir.click(
393
  fn=lambda: "⏳ Corrigindo...", outputs=status
394
  ).then(