CorretorTCC / app.py
GabrielGD's picture
Update app.py
ff2e19e verified
Raw
History Blame Contribute Delete
65.1 kB
import os
import io
import re
import json
import streamlit as st
from docx import Document
from docx.shared import Cm, Pt, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_BREAK
from docx.enum.section import WD_SECTION
from docx.oxml.ns import qn
from docx.oxml import OxmlElement
from pydantic import BaseModel
from typing import List, Optional, Tuple
from google import genai
from google.genai import types
# =====================================================
# CONFIGURAÇÕES GLOBAIS E ESTADO
# =====================================================
st.set_page_config(page_title="Corretor TCC - UNIFAGOC", page_icon="⚖️", layout="wide")
def reset_estado(chave):
if chave in st.session_state:
st.session_state[chave] = None
# Inicialização do estado
for key in ["resultado_auditoria", "doc_revisado_bytes", "doc_formatado_bytes", "relatorio_abnt"]:
if key not in st.session_state:
st.session_state[key] = None
MAX_CHARS_PER_CHUNK = 15000 # Limite de segurança para a janela de contexto
LATINISMOS = [
"habeas corpus", "data venia", "jus puniendi", "jus postulandi",
"mens legis", "ratio decidendi", "obiter dictum", "in casu",
"periculum in mora", "fumus boni iuris", "a quo", "ad quem",
"ex tunc", "ex nunc", "erga omnes", "vacatio legis",
"mutatis mutandis", "strictu sensu", "lato sensu", "sine qua non"
]
# =====================================================
# ESQUEMAS PYDANTIC
# =====================================================
class AuditoriaTCC(BaseModel):
aderencia_percentual: int
diagnostico_geral: str
problema_identificado_no_projeto: str
tcc_respondeu_ao_problema: bool
justificativa_problema: str
objetivos_especificos_identificados: List[str]
objetivos_nao_cumpridos_no_tcc: List[str]
desvios_de_tema_ou_linguica: List[str]
indicios_texto_generico_ia: List[str]
incoerencias_internas: List[str]
nota_tema_problema: float
nota_introducao_metodo: float
nota_estruturacao: float
nota_formatacao_abnt: float
justificativa_notas_banca: str
class ErroTexto(BaseModel):
original: str
corrigido: str
motivo: str
categoria: str
gravidade: str
contexto: str
class ProblemaAcademico(BaseModel):
trecho: str
problema: str
sugestao: str
gravidade: str
class RevisaoInteligente(BaseModel):
correcoes: List[ErroTexto]
problemas_academicos: List[ProblemaAcademico]
termos_estrangeiros_para_italico: List[str]
class ProblemaReferencia(BaseModel):
original: str
erros: List[str]
class ListaReferencias(BaseModel):
referencias: List[ProblemaReferencia]
# Novos Esquemas para o Diagnóstico ABNT (ABA 3)
class AlertaCorrecao(BaseModel):
trecho_original: str
como_deve_ficar: str
justificativa_regra: str
local_aproximado: str
class RelatorioDiagnostico(BaseModel):
erros_estrutura_abnt: List[AlertaCorrecao]
erros_texto_juridico: List[AlertaCorrecao]
erros_referencias: List[AlertaCorrecao]
nota_geral_formatacao: int
# =====================================================
# CLASSE REVISOR IA (Responsável por interações com Gemini)
# =====================================================
class RevisorIA:
def __init__(self):
api_key = st.secrets.get("GEMINI_API_KEY") or os.environ.get("GEMINI_API_KEY")
if not api_key:
st.error("❌ ERRO: Chave GEMINI_API_KEY não configurada nos secrets.")
st.stop()
self.client = genai.Client(api_key=api_key)
def auditar_tcc(self, texto_projeto: str, texto_tcc: str, fase_tcc: str) -> Optional[AuditoriaTCC]:
regra_fase = (
"ATENÇÃO: O aluno está em TCC 1. NÃO penalize a ausência de Resumo e Conclusão."
if "TCC 1" in fase_tcc else "O aluno está em TCC 2. Exija o trabalho completo."
)
prompt = (
f"Como Professor Examinador da UNIFAGOC:\n{regra_fase}\n"
f"=== PRÉ-PROJETO ===\n{texto_projeto}\n"
f"=== TCC ===\n{texto_tcc}\n"
"TAREFA 1: Avalie cruzamento de dados e fuga de tema.\n"
"TAREFA 2: Verifique incoerências internas.\n"
"TAREFA 3: Distribua 80 pontos conforme ficha UNIFAGOC."
)
try:
res = self.client.models.generate_content(
model="gemini-3.5-flash",
contents=prompt,
config=types.GenerateContentConfig(
temperature=0.0,
response_mime_type="application/json",
response_schema=AuditoriaTCC
)
)
return AuditoriaTCC(**json.loads(res.text))
except Exception as e:
st.error(f"Erro na auditoria: {e}")
return None
def revisar_texto(self, texto: str) -> Optional[RevisaoInteligente]:
prompt = (
"Você é um Professor Revisor de TCCs de Direito da UNIFAGOC. "
"IMPORTANTE: O ano atual é maio de 2026. Não aponte citações ou referências de 2024, 2025 ou 2026 como sendo do futuro. "
"Analise o texto e corrija: ORTOGRAFIA, GRAMÁTICA, VÍCIOS JURÍDICOS, COERÊNCIA, ABNT. "
f"Texto: {texto}"
)
try:
res = self.client.models.generate_content(
model="gemini-3.5-flash",
contents=prompt,
config=types.GenerateContentConfig(
temperature=0.1,
response_mime_type="application/json",
response_schema=RevisaoInteligente
)
)
return RevisaoInteligente(**json.loads(res.text))
except Exception as e:
st.error(f"Erro na revisão: {e}")
return None
def validar_referencias(self, referencias: List[str]) -> ListaReferencias:
refs_str = '\n'.join(referencias)
prompt = (
"Analise as referências abaixo **APENAS PARA IDENTIFICAR ERROS** (NÃO reescreva, NÃO invente dados!).\n"
"IMPORTANTE: Estamos em maio de 2026. Obras de 2024, 2025 e 2026 estão perfeitamente válidas e não são obras do futuro.\n"
"Para cada uma, retorne:\n"
"- original: texto original\n"
"- erros: lista de erros (ex: [\"título não em negrito\", \"autor não invertido\"])\n\n"
f"Referências:\n{refs_str}"
)
try:
res = self.client.models.generate_content(
model="gemini-3.5-flash",
contents=prompt,
config=types.GenerateContentConfig(
temperature=0.0,
response_mime_type="application/json",
response_schema=ListaReferencias
)
)
return ListaReferencias(**json.loads(res.text))
except Exception as e:
st.error(f"Erro na validação de referências: {e}")
return ListaReferencias(referencias=[])
def gerar_diagnostico_abnt(self, texto: str) -> Optional[RelatorioDiagnostico]:
prompt = f"""
Você é um Inspetor Acadêmico Implacável de TCCs de Direito da UNIFAGOC.
Sua missão não é reescrever o texto, mas gerar um laudo diagnóstico rigoroso apontando os erros.
IMPORTANTE: O ano atual é maio de 2026. Nunca afirme que referências ou dados de 2024 a 2026 são do futuro.
Para evitar estourar o limite, foque nos 5 piores erros de cada categoria.
Analise o texto abaixo e preencha o relatório:
1. ESTRUTURA E ABNT: Procure parágrafos com formatação incorreta, citações longas sem destaque visual, falta de itálico em latinismos, etc.
2. TEXTO JURÍDICO: Aponte erros de estilo, repetições viciosas, e inadequação acadêmica.
3. REFERÊNCIAS: Aponte exatamente qual referência está fora do padrão ABNT (ex: título sem destaque, autor minúsculo).
Para cada erro, cite o trecho original exato, sugira como deve ficar, explique a regra de forma clara e indique a localização.
Dê uma nota geral de saúde da formatação (0 a 100).
TEXTO DO ALUNO:
{texto}
"""
try:
res = self.client.models.generate_content(
model="gemini-3.5-flash",
contents=prompt,
config=types.GenerateContentConfig(
temperature=0.1,
response_mime_type="application/json",
response_schema=RelatorioDiagnostico
)
)
return RelatorioDiagnostico(**json.loads(res.text))
except Exception as e:
st.error(f"Erro no diagnóstico: {e}")
return None
# =====================================================
# CLASSE FORMATADOR UNIFAGOC (Responsável por formatação DOCX)
# =====================================================
class FormatadorUNIFAGOC:
@staticmethod
def aplicar_margens(doc: Document):
"""Aplica margens oficiais UNIFAGOC (A4: 21x29.7cm)"""
for section in doc.sections:
section.page_height = Cm(29.7)
section.page_width = Cm(21.0)
section.top_margin = Cm(3.0)
section.left_margin = Cm(3.0)
section.bottom_margin = Cm(2.0)
section.right_margin = Cm(2.0)
section.header_distance = Cm(0.5)
section.footer_distance = Cm(0.5)
@staticmethod
def adicionar_paginacao(doc: Document):
"""Adiciona numeração de páginas no rodapé (centralizado) - XML correto"""
for i, section in enumerate(doc.sections):
if i == 0: # Ignora capa/folha de rosto
continue
footer = section.footer
para = footer.paragraphs[0] if footer.paragraphs else footer.add_paragraph()
para.alignment = WD_ALIGN_PARAGRAPH.CENTER
# Adiciona campo PAGE via XML
run = para.add_run()
fldChar = OxmlElement('w:fldChar')
fldChar.set(qn('w:fldCharType'), 'begin')
run._r.append(fldChar)
instrText = OxmlElement('w:instrText')
instrText.set(qn('xml:space'), 'preserve')
instrText.text = 'PAGE'
run._r.append(instrText)
fldChar = OxmlElement('w:fldChar')
fldChar.set(qn('w:fldCharType'), 'end')
run._r.append(fldChar)
for run in para.runs:
run.font.name = 'Calibri'
run.font.size = Pt(10)
@staticmethod
def formatar_figuras_tabelas(doc: Document):
"""Formata figuras, tabelas e quadros"""
for para in doc.paragraphs:
texto = para.text.strip()
if re.match(r'^(Figura|Tabela|Quadro)\s+\d+', texto, re.IGNORECASE):
para.alignment = WD_ALIGN_PARAGRAPH.CENTER
for run in para.runs:
run.font.name = 'Calibri'
run.font.size = Pt(12)
run.bold = True
# Formata legenda
idx = doc.paragraphs.index(para)
if idx + 1 < len(doc.paragraphs):
next_para = doc.paragraphs[idx + 1]
for run in next_para.runs:
run.font.name = 'Calibri'
run.font.size = Pt(11)
# Formata tabelas internas
for table in doc.tables:
for row in table.rows:
for cell in row.cells:
for para in cell.paragraphs:
para.alignment = WD_ALIGN_PARAGRAPH.LEFT
para.paragraph_format.line_spacing = 1.0
for run in para.runs:
run.font.name = 'Calibri'
run.font.size = Pt(11)
@staticmethod
def formatar_imagens(doc: Document):
"""Centraliza imagens (inline shapes)"""
for para in doc.paragraphs:
if para._p.xpath('.//w:drawing') or para._p.xpath('.//w:pic'):
para.alignment = WD_ALIGN_PARAGRAPH.CENTER
@staticmethod
def formatar_notas_rodape(doc: Document):
"""Formata notas de rodapé (¹, ², ³) representadas no texto principal"""
for para in doc.paragraphs:
if re.search(r'[\d\^]+$', para.text.strip()):
for run in para.runs:
run.font.size = Pt(8)
run.font.superscript = True
@staticmethod
def substituir_texto_preservando_formato(para, original: str, corrigido: str) -> int:
"""
Substitui texto PRESERVANDO formatação.
Tenta substituir dentro do próprio run primeiro para preservar hiperlinks e estilos mistos.
"""
texto_completo = "".join(run.text for run in para.runs)
if original not in texto_completo:
return 0
# Tentativa 1: Substituição cirúrgica no mesmo run (preserva 100% da formatação)
for run in para.runs:
if original in run.text:
run.text = run.text.replace(original, corrigido, 1)
return 1
# Tentativa 2: Fallback caso a palavra esteja quebrada entre runs diferentes
pos = texto_completo.find(original)
if pos == -1:
return 0
antes = texto_completo[:pos]
depois = texto_completo[pos + len(original):]
estilo_base = para.style
alinhamento = para.paragraph_format.alignment
para.clear()
if antes:
para.add_run(antes)
run_corrigido = para.add_run(corrigido)
run_corrigido.font.name = 'Calibri'
run_corrigido.font.size = Pt(12)
if depois:
para.add_run(depois)
if estilo_base:
para.style = estilo_base
if alinhamento:
para.paragraph_format.alignment = alinhamento
return 1
@staticmethod
def formatar_documento(doc: Document, revisor: RevisorIA) -> List[ProblemaReferencia]:
"""Função principal de formatação estrutural (ABNT UNIFAGOC)"""
FormatadorUNIFAGOC.aplicar_margens(doc)
FormatadorUNIFAGOC.adicionar_paginacao(doc)
FormatadorUNIFAGOC.formatar_figuras_tabelas(doc)
FormatadorUNIFAGOC.formatar_imagens(doc)
FormatadorUNIFAGOC.formatar_notas_rodape(doc)
for para in doc.paragraphs:
texto = para.text.strip()
if not texto:
continue
# --- REFERÊNCIAS ---
if re.search(r"REFER[ÊE]NCIAS", texto, re.IGNORECASE):
para.alignment = WD_ALIGN_PARAGRAPH.CENTER
para.paragraph_format.space_before = Pt(12)
para.paragraph_format.space_after = Pt(6)
for run in para.runs:
run.font.name = 'Calibri'
run.font.size = Pt(12)
run.bold = True
continue
# --- RESUMO ---
if texto.upper() == "RESUMO":
para.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
para.paragraph_format.space_before = Pt(12)
para.paragraph_format.space_after = Pt(0)
para.paragraph_format.line_spacing = 1.5
for run in para.runs:
run.font.name = 'Calibri'
run.font.size = Pt(11)
run.bold = True
continue
# --- PALAVRAS-CHAVE ---
if texto.upper().startswith("PALAVRAS-CHAVE"):
para.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
para.paragraph_format.space_before = Pt(6)
para.paragraph_format.space_after = Pt(0)
para.paragraph_format.line_spacing = 1.5
for run in para.runs:
run.font.name = 'Calibri'
run.font.size = Pt(11)
if "PALAVRAS-CHAVE" in run.text.upper():
run.bold = True
continue
# --- TÍTULOS E SEÇÕES ---
nivel = FormatadorUNIFAGOC.detectar_nivel_secao(texto)
if nivel > 0 or FormatadorUNIFAGOC.detectar_titulo_ou_subtitulo(texto) == "titulo":
para.alignment = WD_ALIGN_PARAGRAPH.LEFT
para.paragraph_format.space_before = Pt(12)
para.paragraph_format.space_after = Pt(6)
for run in para.runs:
run.font.name = 'Calibri'
run.font.size = Pt(12)
run.bold = True
if nivel == 3:
run.italic = True
if nivel == 2:
para.paragraph_format.left_indent = Cm(0.5)
continue
# --- CITAÇÕES LONGAS ---
if FormatadorUNIFAGOC.detectar_citacao_longa(para):
para.paragraph_format.left_indent = Cm(4.0)
para.paragraph_format.line_spacing = 1.0
para.paragraph_format.space_before = Pt(6)
para.paragraph_format.space_after = Pt(6)
for run in para.runs:
run.font.name = 'Calibri'
run.font.size = Pt(11)
continue
# --- LISTAS ---
if FormatadorUNIFAGOC.detectar_lista(texto):
para.paragraph_format.left_indent = Cm(1.25)
para.paragraph_format.first_line_indent = Cm(0)
para.paragraph_format.space_before = Pt(0)
para.paragraph_format.space_after = Pt(0)
para.paragraph_format.line_spacing = 1.5
for run in para.runs:
run.font.name = 'Calibri'
run.font.size = Pt(12)
continue
# --- CORPO DO TEXTO PADRÃO ---
para.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
para.paragraph_format.first_line_indent = Cm(1.25)
para.paragraph_format.line_spacing = 1.5
para.paragraph_format.space_before = Pt(0)
para.paragraph_format.space_after = Pt(0)
for run in para.runs:
run.font.name = 'Calibri'
run.font.size = Pt(12)
# 4. Valida referências extraídas no documento
refs = FormatadorUNIFAGOC.extrair_referencias(doc)
if refs:
lista_refs = revisor.validar_referencias(refs)
return lista_refs.referencias
return []
@staticmethod
def formatacao_cruzada_indices(para, texto_alvo: str, aplicar_italico: bool = False, aplicar_negrito: bool = False) -> int:
aplicacoes = 0
if aplicar_italico:
matches = list(re.finditer(r'\b' + re.escape(texto_alvo) + r'\b', para.text, re.IGNORECASE))
else:
idx = para.text.find(texto_alvo)
matches = [(idx, idx + len(texto_alvo))] if idx != -1 else []
for match in matches:
pos_ini, pos_fim = match if isinstance(match, tuple) else (match.start(), match.end())
if pos_ini == -1:
continue
pos_atual = 0
for run in para.runs:
fim_run = pos_atual + len(run.text)
if pos_atual < pos_fim and fim_run > pos_ini:
if aplicar_italico:
run.italic = True
if aplicar_negrito:
run.bold = True
aplicacoes += 1
pos_atual += len(run.text)
return aplicacoes
@staticmethod
def detectar_nivel_secao(texto: str) -> int:
match = re.match(r'^(\d+(?:\.\d+)*)\s+', texto)
if not match: return 0
return match.group(1).count('.') + 1
@staticmethod
def detectar_titulo_ou_subtitulo(texto: str) -> Optional[str]:
SECOES_ESPECIAIS = ["INTRODUÇÃO", "CONSIDERAÇÕES FINAIS", "REFERÊNCIAS", "RESUMO", "PALAVRAS-CHAVE"]
texto_upper = texto.upper()
if any(sec in texto_upper for sec in SECOES_ESPECIAIS): return "titulo"
if FormatadorUNIFAGOC.detectar_nivel_secao(texto) > 0: return "titulo"
if texto.isupper() and len(texto) < 150 and not FormatadorUNIFAGOC.detectar_nivel_secao(texto): return "titulo"
return None
@staticmethod
def detectar_citacao_longa(para) -> bool:
if para.paragraph_format.left_indent and para.paragraph_format.left_indent >= Cm(3.5):
return True
texto = para.text.strip()
if len(texto.split()) >= 40:
return True
return False
@staticmethod
def detectar_lista(texto: str) -> bool:
return bool(re.match(r'^(\d+[\)\.]|[a-zA-Z][\)\.]|[IVXLCDM]+[\)\.]|[-–])\s', texto))
@staticmethod
def extrair_referencias(doc: Document) -> List[str]:
refs = []
em_refs = False
for para in doc.paragraphs:
texto = para.text.strip()
if re.search(r"REFER[ÊE]NCIAS", texto, re.IGNORECASE):
em_refs = True
continue
if em_refs and (len(texto) < 10 or texto.isupper()):
break
if em_refs:
refs.append(texto)
return refs
@staticmethod
def extrair_texto_docx(arquivo_upload) -> str:
doc = Document(arquivo_upload)
return "\n\n".join([para.text.strip() for para in doc.paragraphs if para.text.strip()])
@staticmethod
def docx_to_bytes(doc: Document) -> bytes:
byte_io = io.BytesIO()
doc.save(byte_io)
return byte_io.getvalue()
@staticmethod
def estimar_paginas(doc: Document) -> int:
palavras_totais = sum(
len(para.text.split()) for para in doc.paragraphs
if not re.search(r"^(?:\d+\.?\s*)?REFER[ÊE]NCIAS", para.text, re.IGNORECASE)
)
return max(1, palavras_totais // 350)
# =====================================================
# CLASSE AUDITOR TCC (Responsável pela lógica da aplicação)
# =====================================================
class AuditorTCC:
def __init__(self, revisor: RevisorIA):
self.revisor = revisor
def auditar(self, arq_projeto, arq_tcc, fase_tcc: str) -> Optional[AuditoriaTCC]:
texto_projeto = FormatadorUNIFAGOC.extrair_texto_docx(arq_projeto)
texto_tcc = FormatadorUNIFAGOC.extrair_texto_docx(arq_tcc)
return self.revisor.auditar_tcc(texto_projeto, texto_tcc, fase_tcc)
def revisar(self, arq_rev) -> Tuple[Document, RevisaoInteligente, List[str]]:
doc = Document(arq_rev)
texto_completo = FormatadorUNIFAGOC.extrair_texto_docx(arq_rev)
erros_locais = self.verificar_erros_locais(texto_completo)
# Chunking seguro respeitando parágrafos
chunks = self.dividir_em_chunks(texto_completo)
resultados_revisao = []
for chunk in chunks:
resultado = self.revisor.revisar_texto(chunk)
if resultado:
resultados_revisao.append(resultado)
revisao_consolidada = self.consolidar_revisoes(resultados_revisao)
self.aplicar_correcoes(doc, revisao_consolidada)
return doc, revisao_consolidada, erros_locais
def verificar_erros_locais(self, texto: str) -> List[str]:
erros = []
if re.search(r"\s{2,}", texto): erros.append("Espaços duplos detectados no documento.")
if re.search(r",\s*,", texto): erros.append("Vírgulas duplicadas detectadas (ex: ',,').")
if re.search(r"[.!?]{2,}", texto) and not "..." in texto: erros.append("Pontuação repetida indevida.")
if re.search(r"\b(\w+)\s+\1\b", texto, re.IGNORECASE): erros.append("Palavras repetidas sequencialmente.")
if re.search(r"\([A-ZÀ-Ú\s]+,\s*\)", texto): erros.append("Citação ABNT com ano faltando.")
return erros
def dividir_em_chunks(self, texto: str) -> List[str]:
"""Divide texto em chunks respeitando a quebra de parágrafos"""
paragrafos = texto.split('\n\n')
chunks = []
chunk_atual = ""
for p in paragrafos:
if len(chunk_atual) + len(p) + 2 < MAX_CHARS_PER_CHUNK:
chunk_atual += p + "\n\n"
else:
if chunk_atual:
chunks.append(chunk_atual.strip())
chunk_atual = p + "\n\n"
if chunk_atual:
chunks.append(chunk_atual.strip())
return chunks
def consolidar_revisoes(self, revisoes: List[RevisaoInteligente]) -> RevisaoInteligente:
if not revisoes:
return RevisaoInteligente(correcoes=[], problemas_academicos=[], termos_estrangeiros_para_italico=[])
todas_correcoes = []
todos_problemas = []
todos_termos = []
for revisao in revisoes:
todas_correcoes.extend(revisao.correcoes)
todos_problemas.extend(revisao.problemas_academicos)
todos_termos.extend(revisao.termos_estrangeiros_para_italico)
return RevisaoInteligente(
correcoes=todas_correcoes,
problemas_academicos=todos_problemas,
termos_estrangeiros_para_italico=list(set(todos_termos))
)
def aplicar_correcoes(self, doc: Document, revisao: RevisaoInteligente):
for para in doc.paragraphs:
for correcao in revisao.correcoes:
FormatadorUNIFAGOC.substituir_texto_preservando_formato(para, correcao.original, correcao.corrigido)
for table in doc.tables:
for row in table.rows:
for cell in row.cells:
for para in cell.paragraphs:
for correcao in revisao.correcoes:
FormatadorUNIFAGOC.substituir_texto_preservando_formato(para, correcao.original, correcao.corrigido)
termos_para_italico = set(LATINISMOS + revisao.termos_estrangeiros_para_italico)
for para in doc.paragraphs:
for termo in termos_para_italico:
FormatadorUNIFAGOC.formatacao_cruzada_indices(para, termo, aplicar_italico=True)
for table in doc.tables:
for row in table.rows:
for cell in row.cells:
for para in cell.paragraphs:
for termo in termos_para_italico:
FormatadorUNIFAGOC.formatacao_cruzada_indices(para, termo, aplicar_italico=True)
# =====================================================
# INTERFACE STREAMLIT
# =====================================================
st.title("⚖️ Assistente Acadêmico - Direito UNIFAGOC")
aba_auditoria, aba_revisao, aba_abnt, aba_prompt, aba_feedback = st.tabs([
"🔍 Auditoria e Notas",
"🧠 Revisão Inteligente",
"📋 Diagnóstico ABNT",
"🤖 Prompt Mestre",
"👨‍🏫 Feedback Professor"
])
# Inicialização centralizada das classes
revisor_ia = RevisorIA()
auditor = AuditorTCC(revisor_ia)
formatador = FormatadorUNIFAGOC()
# ABA 1: AUDITORIA
with aba_auditoria:
st.markdown("Cruze o **Pré-Projeto** com o **TCC** para identificar fuga do tema e gerar a **Nota da Banca (80 pontos)**.")
fase_tcc = st.radio("Fase atual:", ["TCC 1 (Apenas Introdução e Capítulo 1)", "TCC 2 (TCC Completo)"], horizontal=True)
col1, col2 = st.columns(2)
with col1:
arq_projeto = st.file_uploader("1. Pré-Projeto (.docx)", type=["docx"], key="proj")
with col2:
arq_tcc = st.file_uploader("2. TCC (.docx)", type=["docx"], key="tcc_aud")
if st.button("🔍 Iniciar Auditoria", type="primary"):
if not arq_projeto or not arq_tcc:
st.warning("⚠️ Envie os DOIS arquivos para realizar a comparação.")
else:
with st.status("Analisando coerência jurídica...", expanded=True) as status:
resultado = auditor.auditar(arq_projeto, arq_tcc, fase_tcc)
if resultado:
st.session_state.resultado_auditoria = resultado
status.update(label="✅ Avaliação concluída!", state="complete", expanded=False)
else:
status.update(label="❌ Falha na avaliação.", state="error")
if st.session_state.resultado_auditoria:
dados = st.session_state.resultado_auditoria
st.header("🏆 Parecer Oficial da Banca UNIFAGOC")
# 1. CÁLCULO DA NOTA COM FEEDBACK VISUAL
nota_total = sum([
dados.nota_tema_problema,
dados.nota_introducao_metodo,
dados.nota_estruturacao,
dados.nota_formatacao_abnt
])
# Se a nota for maior ou igual a 56 (70% de 80), fica verde. Se não, vermelho.
if nota_total >= 56:
st.success(f"### 🎉 NOTA FINAL ESTIMADA: {nota_total}/80 PONTOS")
else:
st.error(f"### ⚠️ NOTA FINAL ESTIMADA: {nota_total}/80 PONTOS (Risco de Reprovação)")
st.info(f"**💡 Parecer Geral do Examinador:** {dados.justificativa_notas_banca}")
st.divider()
# 2. DISTRIBUIÇÃO DAS NOTAS (Métricas)
st.subheader("📊 Distribuição de Pontos")
c1, c2, c3, c4 = st.columns(4)
c1.metric("Tema/Problema", f"{dados.nota_tema_problema}/20")
c2.metric("Intro/Método", f"{dados.nota_introducao_metodo}/20")
c3.metric("Estrutura/Linguagem", f"{dados.nota_estruturacao}/20")
c4.metric("Formatação/ABNT", f"{dados.nota_formatacao_abnt}/20")
st.divider()
# 3. ANÁLISE DE ADERÊNCIA E CONTEÚDO (Verde vs Vermelho)
st.subheader("🎯 Cruzamento: Projeto vs. TCC")
# Barra de progresso visual para a aderência
st.write(f"**Índice de Fidelidade ao Projeto:** {dados.aderencia_percentual}%")
st.progress(dados.aderencia_percentual / 100)
st.write("") # Espaçamento
col_a, col_b = st.columns(2)
with col_a:
st.markdown("#### ❓ Resposta ao Problema")
if dados.tcc_respondeu_ao_problema:
st.success(f"**✅ TCC Respondeu:** {dados.justificativa_problema}")
else:
st.error(f"**❌ Fuga do Problema:** {dados.justificativa_problema}")
st.markdown("#### 📋 Cumprimento de Objetivos")
# Filtra os objetivos que foram cumpridos subtraindo os não cumpridos
objetivos_cumpridos = [
obj for obj in dados.objetivos_especificos_identificados
if obj not in dados.objetivos_nao_cumpridos_no_tcc
]
# Exibe os Verdes (Cumpridos)
if objetivos_cumpridos:
for obj in objetivos_cumpridos:
st.success(f"✅ **Atingido:** {obj}")
# Exibe os Vermelhos (Falhos)
if dados.objetivos_nao_cumpridos_no_tcc:
for obj in dados.objetivos_nao_cumpridos_no_tcc:
st.error(f"❌ **Faltou Abordar:** {obj}")
elif not dados.objetivos_nao_cumpridos_no_tcc and objetivos_cumpridos:
st.success("✨ **Perfeito!** Todos os objetivos específicos foram mapeados e cumpridos.")
with col_b:
st.markdown("#### 🚨 Filtro Anti-Embromação e Incoerências")
teve_problema_grave = False
if dados.incoerencias_internas:
teve_problema_grave = True
for incoerencia in dados.incoerencias_internas:
st.error(f"**Incoerência Estrutural:** {incoerencia}")
if dados.desvios_de_tema_ou_linguica:
teve_problema_grave = True
for desvio in dados.desvios_de_tema_ou_linguica:
st.warning(f"**Fuga de Tema / Enrolação:** {desvio}")
if dados.indicios_texto_generico_ia:
teve_problema_grave = True
for ia in dados.indicios_texto_generico_ia:
st.error(f"**Indício de Texto Genérico/IA:** {ia}")
if not teve_problema_grave:
st.success("✅ **Excelente!** O texto está fluido, sem contradições internas, sem enrolação e com linguagem autêntica.")
# ABA 2: REVISÃO INTELIGENTE
with aba_revisao:
st.markdown("Revisão de **Ortografia**, **Gramática**, **Coerência**, **Vícios Jurídicos** e **Anti-IA**.")
arq_rev = st.file_uploader("Envie o TCC (.docx)", type=["docx"], key="revisao")
if st.button("🧠 Realizar Revisão"):
if arq_rev:
with st.status("Processando revisão...", expanded=True) as status:
doc_rev, revisao, erros_locais = auditor.revisar(arq_rev)
for erro in erros_locais:
st.warning(f"⚠️ {erro}")
if revisao.problemas_academicos:
st.subheader("🚨 Problemas de Redação Detectados")
for prob in revisao.problemas_academicos:
st.error(f"**[{prob.gravidade.upper()}] {prob.problema}**\n\nTrecho: '{prob.trecho}'\n\nSugestão: {prob.sugestao}")
# --- NOVO BLOCO: Mostrando as alterações realizadas no texto ---
if revisao.correcoes:
st.subheader("📝 Registro de Alterações Automáticas")
with st.expander("Ver detalhes das correções aplicadas ao texto"):
for c in revisao.correcoes:
st.markdown(f"❌ **Original:** `{c.original}`")
st.markdown(f"✅ **Corrigido:** `{c.corrigido}`")
st.caption(f"💡 *Motivo:* {c.motivo} | *Categoria:* {c.categoria}")
st.divider()
st.session_state.doc_revisado_bytes = formatador.docx_to_bytes(doc_rev)
st.session_state.nome_arq_rev = arq_rev.name.replace('.docx', '') + "_Revisado.docx"
status.update(
label=f"✅ Revisão concluída: {len(revisao.correcoes)} correções automáticas registradas, {len(revisao.termos_estrangeiros_para_italico)} itálicos.",
state="complete",
expanded=False
)
if st.session_state.doc_revisado_bytes:
st.download_button(
"⬇️ Baixar TCC Revisado",
data=st.session_state.doc_revisado_bytes,
file_name=st.session_state.nome_arq_rev,
mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document"
)
# ABA 3: DIAGNÓSTICO ABNT (Painel Interativo de Laudos)
with aba_abnt:
st.markdown("Diagnóstico Rigoroso de **Estrutura e ABNT** (Inspetor Acadêmico UNIFAGOC).")
arq_diag = st.file_uploader("Envie o TCC (.docx) para Laudo", type=["docx"], key="abnt_file")
if st.button("📋 Gerar Laudo Diagnóstico"):
if arq_diag:
with st.status("Inspecionando documento...", expanded=True) as status:
texto_completo = formatador.extrair_texto_docx(arq_diag)
# Para evitar limite de output JSON, a IA foca nos principais erros do arquivo
relatorio = revisor_ia.gerar_diagnostico_abnt(texto_completo)
if relatorio:
st.session_state.relatorio_abnt = relatorio
status.update(label="✅ Laudo gerado com sucesso!", state="complete", expanded=False)
else:
status.update(label="❌ Falha na inspeção.", state="error")
if st.session_state.get("relatorio_abnt"):
relatorio = st.session_state.relatorio_abnt
st.header("📋 Laudo de Revisão de Formatação")
# Dashboard de Saúde ABNT
if relatorio.nota_geral_formatacao >= 70:
st.success(f"### Saúde da Formatação: {relatorio.nota_geral_formatacao}/100")
else:
st.error(f"### Saúde da Formatação: {relatorio.nota_geral_formatacao}/100")
st.progress(relatorio.nota_geral_formatacao / 100)
st.divider()
# Função para renderizar os cards do Dashboard de Erros
def renderizar_cards_erro(lista_erros, titulo, icone):
st.subheader(f"{icone} {titulo}")
if not lista_erros:
st.success("✅ Nenhum erro encontrado nesta categoria!")
for erro in lista_erros:
with st.expander(f"📍 Local aproximado: {erro.local_aproximado}"):
st.error(f"**Trecho com erro:**\n\n{erro.trecho_original}")
st.success(f"**Como deve ficar:**\n\n{erro.como_deve_ficar}")
st.info(f"**Regra UNIFAGOC aplicada:** {erro.justificativa_regra}")
st.write("") # Espaçamento
renderizar_cards_erro(relatorio.erros_estrutura_abnt, "Problemas de ABNT e Estrutura", "📐")
renderizar_cards_erro(relatorio.erros_texto_juridico, "Problemas de Texto Jurídico", "✍️")
renderizar_cards_erro(relatorio.erros_referencias, "Problemas nas Referências Bibliográficas", "📚")
# ABA 4: PROMPT MESTRE
with aba_prompt:
st.markdown("### 🤖 Prompt Mestre - Padrão UNIFAGOC")
prompt_pronto = """
Você é um revisor acadêmico especializado nas normas ABNT e nas diretrizes da UNIFAGOC.
Sua tarefa é receber um texto bruto e formatá-lo EXATAMENTE conforme as especificações
técnicas abaixo, sem omitir nenhuma regra, sem inventar informações, e sem alterar o
conteúdo original (apenas a estrutura e a formatação).
Siga cada item deste guia na ordem apresentada.
════════════════════════════════════════════════════════════
BLOCO 1 — CONFIGURAÇÃO DE PÁGINA
════════════════════════════════════════════════════════════
▸ Tamanho do papel: A4 (210 × 297 mm)
▸ Margens:
- Superior: 3 cm
- Esquerda: 3 cm
- Inferior: 2 cm
- Direita: 2 cm
▸ Cabeçalho: posicionado a 0,5 cm da borda superior da página
▸ Rodapé: posicionado a 0,5 cm da borda inferior da página
▸ Orientação: retrato (portrait)
▸ Extensão total do artigo: entre 10 e 18 páginas
════════════════════════════════════════════════════════════
BLOCO 2 — NUMERAÇÃO DE PÁGINAS
════════════════════════════════════════════════════════════
▸ Localização: cabeçalho (não no rodapé)
▸ Alinhamento: direita (ou conforme padrão do template)
▸ Formato: arábico (1, 2, 3...)
▸ Início da contagem: página 1
▸ IMPORTANTE: a PRIMEIRA página NÃO exibe o número (cabeçalho da
primeira página fica em branco). O número aparece a partir da 2ª página.
════════════════════════════════════════════════════════════
BLOCO 3 — FONTE PADRÃO DO DOCUMENTO
════════════════════════════════════════════════════════════
▸ Fonte exclusiva em todo o documento: Calibri
▸ Tamanho padrão do corpo do texto: 12 pt
▸ Idioma configurado: Português (Brasil)
▸ NÃO usar Times New Roman, Arial ou qualquer outra fonte.
════════════════════════════════════════════════════════════
BLOCO 4 — ESTRUTURA OBRIGATÓRIA DO ARTIGO (nesta ordem)
════════════════════════════════════════════════════════════
O artigo deve conter, obrigatoriamente, os seguintes elementos
na ordem abaixo:
1. TÍTULO (e subtítulo, se houver)
2. Nome(s) do(s) autor(es) com afiliações
3. RESUMO + Palavras-chave
4. INTRODUÇÃO
5. Seções de desenvolvimento (numeradas: 1. TÍTULO, 2. TÍTULO...)
6. CONSIDERAÇÕES FINAIS
7. REFERÊNCIAS
════════════════════════════════════════════════════════════
BLOCO 5 — TÍTULO DO ARTIGO
════════════════════════════════════════════════════════════
▸ Fonte: Calibri 12 pt
▸ Estilo: negrito
▸ Alinhamento: centralizado
▸ Capitalização: MAIÚSCULAS (caixa alta) para o título principal
▸ Subtítulo (após dois-pontos): quando houver, em caixa baixa
- Exemplo: "TÍTULO DO ARTIGO: subtítulo do artigo"
▸ Espaçamento antes do título: 0 pt
▸ Espaçamento depois do título: 0 pt
▸ Entrelinhamento: simples (1,0)
▸ Recuos esquerdo e direito: 0 cm
════════════════════════════════════════════════════════════
BLOCO 6 — AUTORES E AFILIAÇÕES
════════════════════════════════════════════════════════════
▸ FORMATO DO NOME DOS AUTORES:
- SOBRENOME, Nome (em negrito, Calibri 12 pt)
- Alinhamento: centralizado
- Separar autores com ponto e vírgula ( ; )
- Após cada nome, inserir letra sobrescrita (a, b, c...) identificando
a afiliação correspondente
- Espaçamento antes: 12 pt | Espaçamento depois: 6 pt
- Exemplo: SILVA, João a ; SOUZA, Maria b
▸ FORMATO DAS AFILIAÇÕES (logo abaixo dos nomes):
- Uma linha por autor, com a letra sobrescrita correspondente (a, b, c...)
- Conteúdo: Titulação – Principal atividade profissional – Instituição – E-mail
- Fonte: Calibri 12 pt, sem negrito
- Alinhamento: centralizado
- Cada afiliação em parágrafo separado
▸ NOTA DE RODAPÉ (TCC Direito – opcional):
- Pode ser inserida nota de rodapé ao nome do(s) autor(es) com
"Informação adicional sobre o(a) autor(a) (opcional)."
- Numeração por algarismos arábicos
════════════════════════════════════════════════════════════
BLOCO 7 — RESUMO
════════════════════════════════════════════════════════════
▸ Rótulo "RESUMO": Calibri 12 pt, negrito
▸ Texto do resumo: Calibri 11 pt, itálico
▸ Alinhamento: justificado (recuos esquerdo e direito = 0 cm)
▸ Estrutura: parágrafo ÚNICO (sem quebras internas)
▸ Extensão: máximo 150 palavras
▸ Conteúdo obrigatório no resumo:
1. Problema de pesquisa
2. Objetivo
3. Método(s) utilizado(s)
4. Resultados/conclusões mais importantes
▸ Entrelinhamento do resumo: simples
▸ PALAVRAS-CHAVE (logo abaixo do resumo, no mesmo bloco):
- Rótulo "Palavras-chave:" em Calibri 12 pt, negrito
- Seguido das palavras-chave em Calibri 12 pt, sem negrito
- Quantidade: de 3 a 5 palavras-chave
- Separação: ponto ( . ) entre cada palavra-chave
- Capitalização: primeira letra de cada palavra em MAIÚSCULA
(as demais letras em minúscula)
- Formato: Palavras-chave: Direito. Previdência. Reforma Constitucional.
- Espaçamento antes das palavras-chave: 6 pt (após o resumo)
════════════════════════════════════════════════════════════
BLOCO 8 — TÍTULOS DAS SEÇÕES E SUBSEÇÕES
════════════════════════════════════════════════════════════
▸ SEÇÕES PRIMÁRIAS (INTRODUÇÃO, 1. TÍTULO, CONSIDERAÇÕES FINAIS, REFERÊNCIAS):
- Fonte: Calibri 12 pt
- Estilo: negrito
- Capitalização: CAIXA ALTA (todas em maiúsculas)
- Numeração: INTRODUÇÃO e CONSIDERAÇÕES FINAIS sem número;
seções de desenvolvimento com número arábico seguido de ponto
(ex: 1. TÍTULO, 2. TÍTULO, 3. TÍTULO...)
- Alinhamento: justificado à esquerda (sem recuo)
- Espaçamento antes: 0 pt
- Espaçamento depois: aproximadamente 6 pt
- Recuos esquerdo e direito: 0 cm
▸ SEÇÕES SECUNDÁRIAS (subseções):
- Fonte: Calibri 12 pt
- Estilo: negrito
- Capitalização: caixa baixa (apenas a primeira letra em maiúscula)
- Numeração: 1.1 Título, 1.2 Título...
- Alinhamento: justificado à esquerda (sem recuo)
▸ SEÇÕES TERCIÁRIAS (sub-subseções):
- Fonte: Calibri 12 pt
- Estilo: negrito + itálico
- Capitalização: caixa baixa
- Numeração: 1.1.1 Título, 1.1.2 Título...
- Alinhamento: justificado à esquerda (sem recuo)
════════════════════════════════════════════════════════════
BLOCO 9 — CORPO DO TEXTO (parágrafos das seções)
════════════════════════════════════════════════════════════
▸ Fonte: Calibri 12 pt
▸ Alinhamento: justificado (margens esquerda e direita alinhadas)
▸ Recuo de primeira linha (parágrafo): 1,25 cm
▸ Recuo esquerdo do parágrafo: 0 cm
▸ Recuo direito do parágrafo: 0 cm
▸ Entrelinhamento: 1,5 (150%)
▸ Espaçamento antes do parágrafo: 0 pt
▸ Espaçamento depois do parágrafo: 0 pt
════════════════════════════════════════════════════════════
BLOCO 10 — CITAÇÕES
════════════════════════════════════════════════════════════
▸ SISTEMA DE CITAÇÃO: AUTOR-data, conforme NBR 10520 (ABNT, 2002)
- Exemplos: (SILVA, 2020); Silva (2020); (SILVA, 2020, p. 45)
▸ CITAÇÕES CURTAS (até 4 linhas):
- Inseridas no corpo do texto
- Entre aspas duplas (" ")
- Mesma fonte e tamanho do parágrafo (Calibri 12)
- Chamada da fonte ao final, entre parênteses: (AUTOR, ano, p. x)
▸ CITAÇÕES LONGAS (mais de 4 linhas — citação em bloco):
- Parágrafo separado do texto principal
- Fonte: Calibri 11 pt (um ponto menor que o corpo do texto)
- Entrelinhamento: SIMPLES (1,0)
- Alinhamento: justificado
- Recuo SOMENTE da margem ESQUERDA: 4 cm (a partir da margem esquerda
da página, que já é 3 cm — portanto, 4 cm adicionais)
- Recuo direito: 0 cm (sem recuo à direita)
- SEM aspas na citação em bloco
- Espaçamento antes e depois da citação em bloco: 1 linha em branco
- A chamada da fonte deve vir ao final, após o ponto final:
(AUTOR, ano, p. x)
- NÃO usar recuo de primeira linha neste parágrafo
════════════════════════════════════════════════════════════
BLOCO 11 — FIGURAS, QUADROS E TABELAS
════════════════════════════════════════════════════════════
▸ Posicionamento: centralizados na página, próximos à sua citação no texto
▸ Numeração: sequencial por tipo (Figura 1, Figura 2; Quadro 1, Quadro 2; Tabela 1...)
▸ TÍTULO (acima da figura/quadro/tabela):
- Formato: "Figura X – Descrição do título"
- Fonte: Calibri 12 pt
- Alinhamento: centralizado
- O rótulo "Figura X" em negrito; o texto do título sem negrito
▸ LEGENDA / FONTE (abaixo da figura/quadro/tabela):
- Formato: "Fonte: AUTOR (ano)." ou "Fonte: elaborado pelo(a) autor(a)."
- Fonte: Calibri 11 pt
- Alinhamento: centralizado
▸ ESPAÇAMENTOS ao redor de figuras/quadros/tabelas:
- Entre o corpo do texto e o TÍTULO: 6 pt
- Entre o título e a figura/quadro/tabela: 3 pt
- Entre a figura/quadro/tabela e a legenda/fonte: 3 pt
- Entre a legenda/fonte e o parágrafo seguinte: 6 pt
════════════════════════════════════════════════════════════
BLOCO 12 — REFERÊNCIAS
════════════════════════════════════════════════════════════
▸ Título da seção: "REFERÊNCIAS" (Calibri 12, negrito, caixa alta, sem numeração)
▸ Norma: ABNT NBR 6023:2018
▸ Fonte das referências: Calibri 11 pt
▸ Entrelinhamento: simples (1,0)
▸ Espaçamento entre referências: 6 pt após cada referência (espaçamento depois)
▸ Alinhamento: à ESQUERDA (justificado à esquerda, sem recuo)
▸ Ordenação: ordem ALFABÉTICA pelo sobrenome do primeiro autor
▸ NÃO numerar as referências
▸ Incluir APENAS as referências efetivamente citadas no texto
▸ DESTAQUE EM NEGRITO: o título do livro, periódico ou veículo de publicação
deve estar em NEGRITO (e somente ele, não o restante da referência)
- Livro: SILVA, João. **Título do Livro**. 2. ed. São Paulo: Editora, 2020.
- Artigo: SOUZA, M. Título do artigo. **Nome da Revista**, v. 2, n. 1, 2021.
- Legislação: BRASIL. Lei nº X. **Diário Oficial da União**, Brasília, 2020.
- Jurisprudência: ACÓRDÃO XXXXXRS. Relator: Nome. **Diário da Justiça**, data.
▸ REGRAS DE INTEGRIDADE: Nunca inventar, modificar ou completar referências,
leis, jurisprudências ou doutrinas não fornecidas no texto original.
▸ EXEMPLOS DE FORMATAÇÃO POR TIPO:
• Livro:
SOBRENOME, Nome. **Título em negrito**: subtítulo. ed. Cidade: Editora, ano.
• Artigo em periódico:
SOBRENOME, Nome. Título do artigo. **Nome do Periódico**, cidade, v. X,
n. X, p. XX-XX, mês abrev. ano. Disponível em: URL. Acesso em: dia mês. ano.
• Dissertação/Tese:
SOBRENOME, Nome. **Título da dissertação**. ano. X f. Dissertação (Mestrado
em ...) – Universidade, Cidade. Disponível em: URL. Acesso em: data.
• Legislação:
BRASIL. [Decreto-Lei / Lei] nº X, de data. Ementa. **Diário Oficial da União**,
Brasília, data. Disponível em: URL. Acesso em: data.
• Jurisprudência:
TRIBUNAL/ACÓRDÃO nº X. Relator: Nome. Câmara/Turma. Data de Julgamento:
data. **Nome do Diário**, data.
• Documento eletrônico/site:
SOBRENOME, Nome ou INSTITUIÇÃO. Ano (se houver). Título. **Veículo/Site**.
Disponível em: URL. Acesso em: mês. ano.
════════════════════════════════════════════════════════════
BLOCO 13 — TERMOS ESTRANGEIROS E ITÁLICO
════════════════════════════════════════════════════════════
▸ Todo termo em língua estrangeira deve ser formatado em ITÁLICO
- Exemplos: habeas corpus, fake news, data mining, marketing, in verbis,
ex officio, ad hoc, ab initio, stricto sensu, lato sensu
▸ Nomes de obras, filmes, jornais e revistas citados no texto: itálico
▸ Palavras usadas com sentido especial ou neologismos: itálico ou aspas simples
════════════════════════════════════════════════════════════
BLOCO 14 — ALINHAMENTOS — RESUMO RÁPIDO
════════════════════════════════════════════════════════════
Elemento │ Alinhamento
──────────────────────────────────┼──────────────────────────
Título do artigo │ Centralizado
Autores e afiliações │ Centralizado
RESUMO (texto) │ Justificado
Palavras-chave │ Justificado à esquerda
Títulos de seções (todos níveis) │ Justificado à esquerda
Corpo do texto (parágrafos) │ Justificado (ambos lados)
Citações longas (em bloco) │ Justificado
Títulos de figuras/tabelas │ Centralizado
Legendas/Fontes de figuras │ Centralizado
REFERÊNCIAS (título) │ Centralizado ou esquerda*
Referências (entradas) │ Esquerda (sem recuo)
* O título "REFERÊNCIAS" pode ser centralizado conforme o template 2025,
mas as entradas da lista são sempre alinhadas à esquerda.
════════════════════════════════════════════════════════════
BLOCO 15 — TAMANHOS DE FONTE — RESUMO RÁPIDO
════════════════════════════════════════════════════════════
Elemento │ Tamanho │ Estilo
──────────────────────────────────┼─────────┼────────────────────
Título do artigo │ 12 pt │ Negrito, MAIÚSCULA
Autores │ 12 pt │ Negrito
Afiliações dos autores │ 12 pt │ Normal
Rótulo "RESUMO" │ 12 pt │ Negrito
Texto do resumo │ 11 pt │ Itálico
Rótulo "Palavras-chave:" │ 12 pt │ Negrito
Palavras-chave (termos) │ 12 pt │ Normal
Títulos de seções primárias │ 12 pt │ Negrito, MAIÚSCULA
Títulos de seções secundárias │ 12 pt │ Negrito
Títulos de seções terciárias │ 12 pt │ Negrito + Itálico
Corpo do texto (parágrafos) │ 12 pt │ Normal
Citações longas (bloco) │ 11 pt │ Normal
Títulos de figuras/tabelas │ 12 pt │ "Figura X" em negrito
Legendas/Fontes de figuras │ 11 pt │ Normal
Entradas de referências │ 11 pt │ Título da obra em negrito
Notas de rodapé │ 10 pt │ Normal (padrão)
════════════════════════════════════════════════════════════
BLOCO 16 — ENTRELINHAMENTOS — RESUMO RÁPIDO
════════════════════════════════════════════════════════════
Elemento │ Entrelinhamento
──────────────────────────────────┼──────────────────
Corpo do texto (parágrafos) │ 1,5 (150%)
Resumo │ Simples (1,0)
Citações longas (bloco) │ Simples (1,0)
Referências (cada entrada) │ Simples (1,0)
Títulos de seções │ Simples (1,0)
════════════════════════════════════════════════════════════
BLOCO 17 — REGRAS DE INTEGRIDADE E CONDUTA
════════════════════════════════════════════════════════════
▸ NÃO inventar, modificar ou criar: leis, artigos de lei, jurisprudências,
doutrinas, autores ou referências que não estejam explicitamente no texto.
▸ NÃO alterar o conteúdo do texto — apenas a estrutura e a formatação.
▸ NÃO remover seções, parágrafos ou informações do original.
▸ SE o texto original não tiver resumo, palavras-chave ou considerações
finais, sinalize que o autor precisa providenciá-los — não os crie.
▸ SE alguma referência estiver incompleta no original, mantenha-a como
está e sinalize a incompletude para o autor.
════════════════════════════════════════════════════════════
BLOCO 18 — PROCESSO PASSO A PASSO (execute nesta ordem)
════════════════════════════════════════════════════════════
PASSO 1 — LEIA o texto completo antes de qualquer ação.
PASSO 2 — CONFIGURE a página: A4, margens (sup 3 cm, esq 3 cm,
inf 2 cm, dir 2 cm), cabeçalho/rodapé a 0,5 cm.
PASSO 3 — CONFIGURE a numeração de página no cabeçalho (direita),
iniciando na página 1, sem número na primeira página.
PASSO 4 — FORMATE o TÍTULO: Calibri 14, negrito, centralizado,
maiúsculas; subtítulo (se houver) em minúsculas.
PASSO 5 — FORMATE os AUTORES e AFILIAÇÕES com letras sobrescritas,
centralizado, Calibri 12, negrito nos nomes.
PASSO 6 — FORMATE o RESUMO: rótulo em negrito, texto em Calibri 11
itálico, parágrafo único, máx. 150 palavras, justificado.
Logo abaixo: "Palavras-chave:" negrito + 3-5 termos separados
por ponto, primeira letra de cada palavra em maiúscula.
PASSO 7 — VERIFIQUE a estrutura: o texto possui INTRODUÇÃO, seções
numeradas (1, 2, 3...), CONSIDERAÇÕES FINAIS e REFERÊNCIAS.
Se faltarem, sinalize ao autor.
PASSO 8 — FORMATE todos os TÍTULOS DE SEÇÃO:
Primárias → Calibri 12, negrito, MAIÚSCULA, sem recuo.
Secundárias → Calibri 12, negrito, caixa baixa.
Terciárias → Calibri 12, negrito + itálico, caixa baixa.
PASSO 9 — FORMATE o CORPO DO TEXTO: Calibri 12, justificado,
recuo de primeira linha 1,25 cm, entrelinhamento 1,5.
PASSO 10 — IDENTIFIQUE e FORMATE as CITAÇÕES:
Curtas (≤ 4 linhas) → no texto, entre aspas duplas.
Longas (> 4 linhas) → bloco separado, Calibri 11,
recuo esquerdo 4 cm, espaçamento simples, sem aspas.
PASSO 11 — COLOQUE os TERMOS ESTRANGEIROS em itálico.
PASSO 12 — FORMATE as REFERÊNCIAS: Calibri 11, espaçamento simples
+ 6 pt entre cada referência, alinhamento à esquerda,
ordem alfabética, título da obra em negrito, sem numeração.
PASSO 13 — REVISE TODO O DOCUMENTO verificando:
□ Fonte Calibri em todo o documento (nenhuma exceção)
□ Margens corretas (3/3/2/2 cm)
□ Numeração de página no cabeçalho (sem número na 1ª pág.)
□ Entrelinhamento 1,5 no corpo; simples no resumo e referências
□ Termos estrangeiros em itálico
□ Títulos de obras nas referências em negrito
□ Referências em ordem alfabética
□ Extensão entre 10 e 18 páginas
════════════════════════════════════════════════════════════
BLOCO 19 — DIFERENÇAS ENTRE OS DOIS TEMPLATES
════════════════════════════════════════════════════════════
▸ TEMPLATE TCC DIREITO 2025:
- Destinado a artigos do curso de Direito
- Admite nota de rodapé junto ao nome dos autores com
"Informação adicional sobre Autor/a (opcional)."
- Numeração de notas de rodapé: algarismos arábicos
▸ TEMPLATE TRABALHO DE CURSO 2026:
- Destinado a trabalhos de curso em geral (não exclusivo a Direito)
- SEM nota de rodapé de informações adicionais dos autores
- Estrutura de seções idêntica ao TCC Direito 2025
▸ AMBOS OS TEMPLATES são idênticos em:
- Configuração de página, margens, fontes, tamanhos, espaçamentos,
entrelinhamentos, citações, referências e estrutura geral.
════════════════════════════════════════════════════════════
EXEMPLO DE ESTRUTURA FINAL ESPERADA
════════════════════════════════════════════════════════════
[CABEÇALHO com número de página — exceto na 1ª página]
TÍTULO DO ARTIGO: subtítulo quando houver
← centralizado, Calibri 14, negrito, maiúsculas
SOBRENOME, Nome a ; SOBRENOME, Nome b
← centralizado, Calibri 12, negrito
a Titulação – Função – Instituição – email@email.com
b Titulação – Função – Instituição – email@email.com
← centralizado, Calibri 12
RESUMO Texto do resumo em itálico, parágrafo único, máximo 150 palavras,
contendo o problema, objetivo, método e resultados. [Calibri 11, itálico]
Palavras-chave: Palavra Um. Palavra Dois. Palavra Três.
INTRODUÇÃO
← Calibri 12, negrito, maiúsculas, sem recuo
Texto do parágrafo com recuo de 1,25 cm na primeira linha...
[Calibri 12, justificado, entrelinhamento 1,5]
1. TÍTULO DA SEÇÃO
Texto do parágrafo...
Citação curta: conforme autor, "texto da citação" (AUTOR, ano, p. x).
Citação longa (em bloco):
[4 cm de recuo à esquerda, Calibri 11, espaçamento simples]
Texto da citação com mais de quatro linhas vai aqui, sem aspas,
recuado 4 cm da margem esquerda, em Calibri tamanho 11,
com espaçamento simples. (AUTOR, ano, p. x)
2. TÍTULO DA SEGUNDA SEÇÃO
2.1 Título de subseção em caixa baixa
← Calibri 12, negrito
2.1.1 Título de sub-subseção em caixa baixa
← Calibri 12, negrito + itálico
CONSIDERAÇÕES FINAIS
REFERÊNCIAS
← Calibri 11, negrito, maiúsculas
SOBRENOME, Nome. **Título da Obra em Negrito**. ed. Cidade: Editora, ano.
← Calibri 11, esquerda, 6 pt após cada entrada
---
AGORA APLIQUE TODAS ESTAS REGRAS AO TEXTO FORNECIDO ABAIXO:
[COLE O TEXTO AQUI]
"""
st.code(prompt_pronto, language="markdown")
# ABA 5: FEEDBACK
with aba_feedback:
st.markdown("### 👨‍🏫 Feedback do Professor Examinador")
arq_feedback = st.file_uploader("Envie o TCC (.docx)", type=["docx"], key="feedback")
if st.button("📊 Gerar Relatório"):
if arq_feedback:
with st.spinner("Analisando estilo acadêmico..."):
texto = formatador.extrair_texto_docx(arq_feedback)
prompt_fb = (
"Analise o TCC: ESTRUTURA, CITAÇÕES, PONTOS FORTES/ATENÇÃO, FORMAÇÃO. "
"ATENÇÃO: Estamos em maio de 2026. Publicações de 2024, 2025 ou 2026 estão no prazo e NÃO pertencem ao futuro. "
f"Texto: {texto}"
)
try:
res = revisor_ia.client.models.generate_content(
model="gemini-3.5-flash",
contents=prompt_fb,
config=types.GenerateContentConfig(temperature=0.2)
)
st.write(res.text)
except Exception as e:
st.error(f"❌ Erro: {e}")