GabrielGD commited on
Commit
5bd5b5d
·
verified ·
1 Parent(s): cac5a97

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +225 -115
app.py CHANGED
@@ -1,23 +1,17 @@
1
  import os
 
 
2
  import json
3
  import streamlit as st
4
  from docx import Document
5
  from pydantic import BaseModel
6
-
7
- # ─── IMPORTAÇÕES DO NOVO SDK ─────────────────────────────────────────────────
8
  from google import genai
9
  from google.genai import types
10
 
11
- # ─── 1. CONFIGURAÇÃO DA PÁGINA ───────────────────────────────────────────────
12
- st.set_page_config(
13
- page_title="Auditoria de TCC - UNIFAGOC",
14
- page_icon="⚖️",
15
- layout="wide"
16
- )
17
 
18
- # ─── 2. ESQUEMA DE DADOS ESTREITO (PYDANTIC) ─────────────────────────────────
19
- # O Pydantic é extremamente robusto no novo SDK para forçar a IA a
20
- # devolver o JSON perfeitamente estruturado, sem fugir das regras.
21
  class AuditoriaTCC(BaseModel):
22
  aderencia_percentual: int
23
  diagnostico_geral: str
@@ -29,137 +23,253 @@ class AuditoriaTCC(BaseModel):
29
  desvios_de_tema_ou_linguica: list[str]
30
  indicios_texto_generico_ia: list[str]
31
 
32
- # ─── 3. AUTENTICAÇÃO GEMINI (NOVO PADRÃO) ────────────────────────────────────
33
  api_key = os.environ.get("GEMINI_API_KEY")
34
-
35
  if not api_key:
36
  st.error("❌ ERRO: A variável de ambiente GEMINI_API_KEY não foi encontrada.")
37
  st.stop()
38
 
39
- # A inicialização mudou para o novo cliente
40
  client = genai.Client(api_key=api_key)
41
 
42
- # ─── 4. FUNÇÕES DE EXTRAÇÃO ──────────────────────────────────────────────────
43
  def extrair_texto_docx(arquivo_upload) -> str:
44
- """Extrai o texto mantendo a indicação de parágrafos."""
45
  doc = Document(arquivo_upload)
46
  linhas = [para.text.strip() for para in doc.paragraphs if para.text.strip()]
47
  return "\n\n".join(linhas)
48
 
49
- # ─── 5. INTERFACE DO USUÁRIO ─────────────────────────────────────────────────
50
- st.title("⚖️ Auditoria de Aderência (Projeto vs. TCC)")
51
- st.markdown("""
52
- Esta ferramenta cruza o **Pré-Projeto** com o **TCC** para identificar fuga do tema,
53
- textos genéricos (possível uso indiscriminado de IA) e objetivos não cumpridos.
54
- """)
 
 
 
 
 
 
 
 
55
 
56
- st.divider()
 
 
 
 
57
 
58
- col1, col2 = st.columns(2)
59
- with col1:
60
- arq_projeto = st.file_uploader("1. Envie o Pré-Projeto (.docx)", type=["docx"])
61
- with col2:
62
- arq_tcc = st.file_uploader("2. Envie os Capítulos do TCC (.docx)", type=["docx"])
63
 
64
- st.divider()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
- if st.button("🔍 Iniciar Auditoria Cruzada", type="primary", use_container_width=True):
67
- if not arq_projeto or not arq_tcc:
68
- st.warning("⚠️ Envie os DOIS arquivos para realizar a comparação.")
69
- else:
70
- with st.spinner("Analisando argumentos jurídicos e cruzando metodologias..."):
71
-
72
- texto_projeto = extrair_texto_docx(arq_projeto)
73
- texto_tcc = extrair_texto_docx(arq_tcc)
74
-
75
- prompt = f"""
76
- Você é um Professor Examinador titular da banca de Direito da UNIFAGOC.
77
- Sua tarefa é cruzar o Pré-Projeto de TCC com o TCC efetivamente entregue.
78
-
79
- === PRÉ-PROJETO (O compromisso do aluno) ===
80
- {texto_projeto}
81
-
82
- === TCC (O documento final entregue) ===
83
- {texto_tcc}
84
-
85
- INSTRUÇÕES PARA ANÁLISE:
86
- 1. Identifique explicitamente qual é o 'Problema de Pesquisa' no Pré-Projeto.
87
- 2. Verifique se o TCC apresenta argumentação jurídica que responde a esse problema.
88
- 3. Identifique os 'Objetivos Específicos' no Pré-Projeto.
89
- 4. Aponte quais objetivos específicos foram esquecidos ou mal desenvolvidos no TCC.
90
- 5. Procure ativamente por "fuga de tema" (textos genéricos, leis citadas sem contexto, embromação teórica sem aplicação prática - sintomas clássicos de texto gerado por IA sem revisão humana).
91
- """
92
-
93
- # Nova forma de configurar parâmetros na API
94
- config = types.GenerateContentConfig(
95
- temperature=0.0,
96
- response_mime_type="application/json",
97
- response_schema=AuditoriaTCC,
98
- )
99
-
100
- try:
101
- # O método de geração agora usa o cliente e o modelo mais recente
102
- resposta = client.models.generate_content(
103
- model="gemini-3.5-flash",
104
- contents=prompt,
105
- config=config
106
- )
107
 
108
- # O retorno estruturado via Pydantic já vem formatado,
109
- # mas convertemos o texto JSON gerado para dicionário
110
- dados = json.loads(resposta.text)
 
 
 
 
 
 
 
111
 
112
- st.success("Auditoria concluída com sucesso!")
 
113
 
114
- # Renderização Visual dos Dados
115
- st.header("📊 Relatório de Consistência")
116
 
117
- aderencia = dados.get("aderencia_percentual", 0)
118
- cor = "normal" if aderencia >= 70 else "inverse"
119
- st.metric(label="Índice de Aderência ao Pré-Projeto", value=f"{aderencia}%", delta_color=cor)
120
- st.info(dados.get("diagnostico_geral", ""))
121
 
122
- col_a, col_b = st.columns(2)
 
 
 
 
123
 
124
- with col_a:
125
- st.subheader("🎯 Problema de Pesquisa")
126
- st.write(f"**O que o projeto prometeu:** {dados.get('problema_identificado_no_projeto', '')}")
127
 
128
- if dados.get("tcc_respondeu_ao_problema", False):
129
- st.success(f"✅ **O TCC respondeu:** {dados.get('justificativa_problema', '')}")
130
- else:
131
- st.error(f"❌ **O TCC não respondeu:** {dados.get('justificativa_problema', '')}")
132
-
133
- with col_b:
134
- st.subheader("📋 Objetivos Específicos")
135
- st.write("**O que a IA identificou no projeto:**")
136
- for obj in dados.get("objetivos_especificos_identificados", []):
137
- st.markdown(f"- {obj}")
138
 
139
- falhas = dados.get("objetivos_nao_cumpridos_no_tcc", [])
140
- if falhas:
141
- st.error("**Objetivos esquecidos no TCC:**")
142
- for f in falhas: st.markdown(f"- {f}")
143
- else:
144
- st.success("✅ Todos os objetivos parecem ter sido abordados.")
 
 
 
 
 
 
 
 
 
 
 
 
 
145
 
146
- st.divider()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
 
148
- st.subheader("🚨 Filtro de Qualidade e Anti-Embromação")
 
149
 
150
- fugas = dados.get("desvios_de_tema_ou_linguica", [])
151
- if fugas:
152
- st.warning("**Fugas de Tema Identificadas:**")
153
- for fuga in fugas: st.markdown(f"• {fuga}")
154
- else:
155
- st.success("✅ Sem fugas de tema graves.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
 
157
- indicios_ia = dados.get("indicios_texto_generico_ia", [])
158
- if indicios_ia:
159
- st.error("**Alerta de Texto Genérico (Possível IA sem revisão):**")
160
- for indicio in indicios_ia: st.markdown(f"• {indicio}")
 
 
 
 
 
 
 
 
 
161
  else:
162
- st.success("✅ O texto apresenta coesão acadêmica sem marcas óbvias de automação genérica.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
 
164
- except Exception as e:
165
- st.error(f"Ocorreu um erro no processamento: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
+ import io
3
+ import re
4
  import json
5
  import streamlit as st
6
  from docx import Document
7
  from pydantic import BaseModel
 
 
8
  from google import genai
9
  from google.genai import types
10
 
11
+ # ─── CONFIGURAÇÃO DA PÁGINA ───────────────────────────────────────────────
12
+ st.set_page_config(page_title="Corretor TCC - UNIFAGOC", page_icon="⚖️", layout="wide")
 
 
 
 
13
 
14
+ # ─── ESQUEMAS DE DADOS (PYDANTIC) ─────────────────────────────────────────
 
 
15
  class AuditoriaTCC(BaseModel):
16
  aderencia_percentual: int
17
  diagnostico_geral: str
 
23
  desvios_de_tema_ou_linguica: list[str]
24
  indicios_texto_generico_ia: list[str]
25
 
26
+ # ─── AUTENTICAÇÃO GEMINI ──────────────────────────────────────────────────
27
  api_key = os.environ.get("GEMINI_API_KEY")
 
28
  if not api_key:
29
  st.error("❌ ERRO: A variável de ambiente GEMINI_API_KEY não foi encontrada.")
30
  st.stop()
31
 
 
32
  client = genai.Client(api_key=api_key)
33
 
34
+ # ─── FUNÇÕES AUXILIARES ───────────────────────────────────────────────────
35
  def extrair_texto_docx(arquivo_upload) -> str:
 
36
  doc = Document(arquivo_upload)
37
  linhas = [para.text.strip() for para in doc.paragraphs if para.text.strip()]
38
  return "\n\n".join(linhas)
39
 
40
+ def extrair_referencias_docx(arquivo_upload) -> list:
41
+ doc = Document(arquivo_upload)
42
+ resultado = []
43
+ em_refs = False
44
+ for i, para in enumerate(doc.paragraphs):
45
+ texto = para.text.strip()
46
+ if not texto: continue
47
+ if re.search(r"^(?:\d+\.?\s*)?REFER[ÊE]NCIAS", texto, re.IGNORECASE):
48
+ em_refs = True
49
+ continue
50
+ if em_refs:
51
+ if len(texto) < 120 and texto.isupper() and not texto[0].isdigit(): break
52
+ resultado.append((i, texto))
53
+ return resultado
54
 
55
+ def docx_to_bytes(doc: Document) -> bytes:
56
+ """Converte o documento editado para bytes para o botão de download."""
57
+ byte_io = io.BytesIO()
58
+ doc.save(byte_io)
59
+ return byte_io.getvalue()
60
 
61
+ # ─── INTERFACE PRINCIPAL (ABAS) ───────────────────────────────────────────
62
+ st.title("⚖️ Assistente Acadêmico - Direito UNIFAGOC")
63
+ aba_auditoria, aba_typos, aba_abnt = st.tabs(["🔍 Auditoria (Projeto vs TCC)", "📝 Correção de Ortografia", "📚 Referências ABNT"])
 
 
64
 
65
+ # ==============================================================================
66
+ # ABA 1: AUDITORIA E TCC 1 vs TCC 2
67
+ # ==============================================================================
68
+ with aba_auditoria:
69
+ st.markdown("Cruze o **Pré-Projeto** com o **TCC** para identificar fuga do tema e objetivos não cumpridos.")
70
+
71
+ # OPÇÃO DE FASE DO TCC
72
+ fase_tcc = st.radio(
73
+ "Selecione a fase atual do trabalho:",
74
+ ["TCC 1 (Apenas Introdução e Capítulo 1)", "TCC 2 (TCC Completo com Resumo e Conclusão)"],
75
+ horizontal=True
76
+ )
77
+
78
+ col1, col2 = st.columns(2)
79
+ with col1: arq_projeto = st.file_uploader("1. Envie o Pré-Projeto (.docx)", type=["docx"], key="proj")
80
+ with col2: arq_tcc = st.file_uploader("2. Envie os Capítulos do TCC (.docx)", type=["docx"], key="tcc_aud")
81
 
82
+ if st.button("🔍 Iniciar Auditoria Cruzada", type="primary"):
83
+ if not arq_projeto or not arq_tcc:
84
+ st.warning("⚠️ Envie os DOIS arquivos para realizar a comparação.")
85
+ else:
86
+ with st.spinner("Avaliando argumentos..."):
87
+ texto_projeto = extrair_texto_docx(arq_projeto)
88
+ texto_tcc = extrair_texto_docx(arq_tcc)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
 
90
+ # Regra customizada baseada no TCC 1 ou TCC 2
91
+ regra_fase = ""
92
+ if "TCC 1" in fase_tcc:
93
+ regra_fase = "ATENÇÃO MÁXIMA: O aluno está na disciplina TCC 1. Ele DEVE entregar apenas a Introdução e o Capítulo 1. NÃO penalize e NÃO aponte como erro a ausência de Resumo, Conclusão ou capítulos posteriores."
94
+ else:
95
+ regra_fase = "O aluno está no TCC 2. O trabalho deve estar completo (Resumo, Introdução, Desenvolvimento, Conclusão e Referências)."
96
+
97
+ prompt = f"""
98
+ Você é um Professor Examinador de Direito da UNIFAGOC.
99
+ {regra_fase}
100
 
101
+ === PRÉ-PROJETO ===
102
+ {texto_projeto}
103
 
104
+ === TCC ===
105
+ {texto_tcc}
106
 
107
+ Identifique o Problema, Objetivos e procure por fugas de tema genéricas.
108
+ """
 
 
109
 
110
+ config = types.GenerateContentConfig(
111
+ temperature=0.0,
112
+ response_mime_type="application/json",
113
+ response_schema=AuditoriaTCC,
114
+ )
115
 
116
+ try:
117
+ resposta = client.models.generate_content(model="gemini-3.5-flash", contents=prompt, config=config)
118
+ dados = json.loads(resposta.text)
119
 
120
+ st.success(f"Auditoria concluída para os parâmetros do {fase_tcc.split(' ')[0]}!")
121
+ st.metric(label="Índice de Aderência", value=f"{dados.get('aderencia_percentual')}%")
122
+ st.info(dados.get("diagnostico_geral"))
 
 
 
 
 
 
 
123
 
124
+ c1, c2 = st.columns(2)
125
+ with c1:
126
+ st.subheader("🎯 Problema de Pesquisa")
127
+ st.write(f"**Promessa:** {dados.get('problema_identificado_no_projeto')}")
128
+ if dados.get("tcc_respondeu_ao_problema"):
129
+ st.success(f"✅ O TCC respondeu: {dados.get('justificativa_problema')}")
130
+ else:
131
+ st.error(f"❌ Não respondeu: {dados.get('justificativa_problema')}")
132
+ with c2:
133
+ st.subheader("📋 Objetivos")
134
+ for f in dados.get("objetivos_nao_cumpridos_no_tcc", []): st.error(f"Faltou: {f}")
135
+ if not dados.get("objetivos_nao_cumpridos_no_tcc"): st.success("✅ Objetivos abordados.")
136
+
137
+ st.subheader("🚨 Filtro Anti-Embromação")
138
+ for fuga in dados.get("desvios_de_tema_ou_linguica", []): st.warning(f"Fuga: {fuga}")
139
+ for ia in dados.get("indicios_texto_generico_ia", []): st.error(f"Texto Genérico/IA: {ia}")
140
+
141
+ except Exception as e:
142
+ st.error(f"Erro: {e}")
143
 
144
+ # ==============================================================================
145
+ # ABA 2: CORREÇÃO DE ORTOGRAFIA E DIGITAÇÃO (TYPOS)
146
+ # ==============================================================================
147
+ with aba_typos:
148
+ st.markdown("Identifica e corrige automaticamente palavras duplicadas, erros ortográficos e gramaticais.")
149
+ arq_typos = st.file_uploader("Envie o TCC (.docx) para correção", type=["docx"], key="typos")
150
+
151
+ if st.button("📝 Corrigir Ortografia"):
152
+ if arq_typos:
153
+ with st.spinner("Caçando erros de digitação..."):
154
+ texto = extrair_texto_docx(arq_typos)
155
+ prompt_typos = f"""
156
+ Analise o texto e identifique erros OBJETIVOS (ortografia, gramática, palavras duplicadas).
157
+ NÃO aponte termos em inglês ou jargões jurídicos.
158
+ TEXTO: {texto[:20000]}
159
 
160
+ Retorne um JSON: {{"erros": [{{"original": "trecho errado", "corrigido": "trecho certo", "motivo": "explicacao"}}]}}
161
+ """
162
 
163
+ try:
164
+ res = client.models.generate_content(
165
+ model="gemini-3.5-flash",
166
+ contents=prompt_typos,
167
+ config=types.GenerateContentConfig(temperature=0.1, response_mime_type="application/json")
168
+ )
169
+ erros = json.loads(res.text).get("erros", [])
170
+
171
+ if not erros:
172
+ st.success("🎉 Nenhum erro encontrado! O texto está limpo.")
173
+ else:
174
+ st.warning(f"{len(erros)} erro(s) encontrado(s). Aplicando correções...")
175
+ doc_typos = Document(arq_typos)
176
+ aplicadas = 0
177
+ for e in erros:
178
+ orig = e.get("original", "")
179
+ corr = e.get("corrigido", "")
180
+ st.write(f"❌ `{orig}` ➔ ✅ `{corr}` (*{e.get('motivo')}*)")
181
+
182
+ # Lógica de substituição no Documento
183
+ for para in doc_typos.paragraphs:
184
+ if orig in para.text:
185
+ for run in para.runs:
186
+ if orig in run.text:
187
+ run.text = run.text.replace(orig, corr, 1)
188
+ aplicadas += 1
189
+ break
190
+
191
+ if aplicadas > 0:
192
+ st.success(f"{aplicadas} correções aplicadas no documento!")
193
+ st.download_button(
194
+ label="⬇️ Baixar Documento Corrigido (.docx)",
195
+ data=docx_to_bytes(doc_typos),
196
+ file_name="TCC_Sem_Erros.docx",
197
+ mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document"
198
+ )
199
+ except Exception as e:
200
+ st.error(f"Erro ao processar: {e}")
201
 
202
+ # ==============================================================================
203
+ # ABA 3: FORMATAÇÃO DE REFERÊNCIAS ABNT
204
+ # ==============================================================================
205
+ with aba_abnt:
206
+ st.markdown("Verifica a formatação NBR 6023:2018 e aplica o negrito nos elementos corretos (Ex: Título do livro ou Nome do Periódico).")
207
+ arq_abnt = st.file_uploader("Envie o TCC (.docx) para checar referências", type=["docx"], key="abnt")
208
+
209
+ if st.button("📚 Corrigir Referências"):
210
+ if arq_abnt:
211
+ with st.spinner("Avaliando normas ABNT..."):
212
+ referencias = extrair_referencias_docx(arq_abnt)
213
+ if not referencias:
214
+ st.warning("Nenhuma referência encontrada. Certifique-se de que o título da seção seja 'REFERÊNCIAS'.")
215
  else:
216
+ texto_refs = "\n".join(f"{i+1}. {texto}" for i, (_, texto) in enumerate(referencias))
217
+ prompt_abnt = f"""
218
+ Verifique as referências abaixo seguindo a ABNT NBR 6023:2018.
219
+ Retorne um JSON com a avaliação:
220
+ {{"problemas": [{{"numero": int, "erros": ["erro 1"], "elemento_negrito": "palavra a receber destaque"}}]}}
221
+ REFERÊNCIAS:
222
+ {texto_refs}
223
+ """
224
+
225
+ try:
226
+ res = client.models.generate_content(
227
+ model="gemini-3.5-flash",
228
+ contents=prompt_abnt,
229
+ config=types.GenerateContentConfig(temperature=0.0, response_mime_type="application/json")
230
+ )
231
+ problemas = json.loads(res.text).get("problemas", [])
232
+
233
+ doc_abnt = Document(arq_abnt)
234
+
235
+ # Função interna de negrito adaptada do seu script original
236
+ def aplicar_negrito(doc, refs_problematicas):
237
+ em_refs = False
238
+ ref_idx = 0
239
+ for para in doc.paragraphs:
240
+ texto = para.text.strip()
241
+ if not texto: continue
242
+ if re.search(r"^(?:\d+\.?\s*)?REFER[ÊE]NCIAS", texto, re.IGNORECASE):
243
+ em_refs = True
244
+ continue
245
+ if not em_refs or len(texto) < 10: continue
246
+
247
+ # Aplica a lógica de negrito se a referência foi sinalizada
248
+ nums_prob = {p.get("numero", 0) - 1 for p in refs_problematicas}
249
+ if ref_idx in nums_prob:
250
+ match = re.match(r"^((?:[A-ZÁÉÍÓÚÀÂÊÔÃÕÇ][^.;]+[.;]\s*)+)\s*(.+?)(\.\s+(?:\d+\.\s+ed|[A-Z]).*|$)", para.text)
251
+ if match:
252
+ autoria, titulo = match.group(1).strip(), match.group(2).strip()
253
+ pos_atual, pos_ini, pos_fim = 0, para.text.find(titulo, len(autoria)), 0
254
+ pos_fim = pos_ini + len(titulo)
255
+ for run in para.runs:
256
+ fim_run = pos_atual + len(run.text)
257
+ if pos_atual >= pos_ini and fim_run <= pos_fim: run.bold = True
258
+ elif pos_atual < pos_fim and fim_run > pos_ini: run.bold = True
259
+ pos_atual += len(run.text)
260
+ ref_idx += 1
261
 
262
+ aplicar_negrito(doc_abnt, problemas)
263
+
264
+ for p in problemas:
265
+ st.write(f"⚠️ Referência #{p['numero']} possui erros: {', '.join(p['erros'])}")
266
+
267
+ st.success("Formatação de destaque (negrito) aplicada com base na ABNT!")
268
+ st.download_button(
269
+ label="⬇️ Baixar TCC com Referências Corrigidas (.docx)",
270
+ data=docx_to_bytes(doc_abnt),
271
+ file_name="TCC_ABNT_Corrigido.docx",
272
+ mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document"
273
+ )
274
+ except Exception as e:
275
+ st.error(f"Erro na verificação: {e}")