makanimpovi / app.py
wetedieu's picture
Update app.py
090d8d2 verified
import gradio as gr
import pandas as pd
import re
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
import joblib
import pickle
import os
from datetime import datetime
# ====================== CONFIGURAÇÕES ======================
AREAS = {
"IP": "Instituto de Pesquisa",
"FD": "Faculdade de Direito",
"FE": "Faculdade de Educação",
"Reitoria": "Reitoria"
}
TIPO_MANIFESTACAO = ["Reclamação", "Sugestão"]
# ====================== SISTEMA DE VALIDAÇÃO ======================
class ValidadorManifestacoes:
def __init__(self):
self.carregar_datasets()
self.preparar_modelos()
def carregar_datasets(self):
"""Carrega os datasets de validação"""
# Palavras ofensivas padrão
self.palavras_ofensivas = [
"idiota", "burro", "imbecil", "estupido", "cretino", "analfabeto",
"incompetente", "ignorante", "nojento", "asco", "vergonha", "lixo"
]
# Frases sem sentido padrão
self.frases_sem_sentido = [
"asdfghjkl", "qwertyuiop", "123456789", "aaa bbb ccc",
"teste teste", "xyz abc", "mmmm nnnn oooo"
]
# Padrões de frases incompletas
self.padroes_incompletos = [
"e depois não sei", "mas não", "porque sim", "sem motivo",
"só isso", "nada mais", "e pronto"
]
def preparar_modelos(self):
"""Prepara modelos ML para validação de sentido"""
self.criar_modelo_sentido_padrao()
def criar_modelo_sentido_padrao(self):
"""Cria modelo padrão se não existir"""
# Dados de treino para frases com/sem sentido
frases_com_sentido = [
"gostaria de reclamar do atendimento",
"sugiro melhorar a limpeza",
"o serviço está muito lento",
"precisamos de mais computadores",
"a internet está caindo muito",
"as salas de aula precisam de ar condicionado",
"a biblioteca deveria ficar aberta até mais tarde",
"os banheiros precisam de manutenção urgente"
]
frases_sem_sentido = [
"asdf ghjk qwert",
"123 456 789",
"teste teste teste",
"aaa bbb ccc ddd",
"xyz abc def ghi",
"mmmm nnnn oooo pppp",
"111 222 333 444",
"qqqq wwww eeee rrrr"
]
textos = frases_com_sentido + frases_sem_sentido
labels = [1] * len(frases_com_sentido) + [0] * len(frases_sem_sentido)
self.vectorizer_sentido = TfidfVectorizer(max_features=50)
X = self.vectorizer_sentido.fit_transform(textos)
self.modelo_sentido = LogisticRegression()
self.modelo_sentido.fit(X, labels)
print("✅ Modelo de validação de sentido criado com sucesso!")
def validar_ofensas(self, texto):
"""Verifica palavras ofensivas"""
texto_lower = texto.lower()
ofensas_encontradas = []
for palavra in self.palavras_ofensivas:
if re.search(r'\b' + re.escape(palavra) + r'\b', texto_lower):
ofensas_encontradas.append(palavra)
return {
"tem_ofensa": len(ofensas_encontradas) > 0,
"palavras": ofensas_encontradas,
"nivel": "ALTO" if len(ofensas_encontradas) > 2 else "MÉDIO" if len(ofensas_encontradas) > 0 else "BAIXO"
}
def validar_sentido(self, texto):
"""Verifica se a frase faz sentido usando ML"""
if len(texto.split()) < 3:
return {"tem_problema": True, "tipo": "FRASE_MUITO_CURTA"}
# Verificar padrões de spam
texto_lower = texto.lower()
for padrao in self.frases_sem_sentido:
if padrao in texto_lower:
return {"tem_problema": True, "tipo": "PADRAO_SPAM"}
# Usar modelo ML
try:
X = self.vectorizer_sentido.transform([texto])
predicao = self.modelo_sentido.predict(X)[0]
proba = self.modelo_sentido.predict_proba(X)[0][1]
if predicao == 0 or proba < 0.3:
return {"tem_problema": True, "tipo": "SEM_SENTIDO", "confianca": proba}
else:
return {"tem_problema": False, "tipo": "OK", "confianca": proba}
except Exception as e:
print(f"⚠️ Erro no modelo de sentido: {e}")
return {"tem_problema": False, "tipo": "OK", "confianca": 0.5}
def validar_completude(self, texto):
"""Verifica se a frase está completa"""
texto_limpo = texto.strip()
# Verifica se termina com pontuação
termina_pontuacao = texto_limpo.endswith(('.', '!', '?', ';', ':'))
# Verifica tamanho mínimo
palavras = texto_limpo.split()
tem_tamanho_minimo = len(palavras) >= 5
# Verifica padrões incompletos
texto_lower = texto_limpo.lower()
tem_padrao_incompleto = any(
padrao in texto_lower for padrao in self.padroes_incompletos
)
# Verifica se começa com conectores e não continua
conectores = ["e", "mas", "porque", "então", "porém", "contudo"]
primeira_palavra = palavras[0].lower() if palavras else ""
comeca_conector = primeira_palavra in conectores
problemas = []
if not termina_pontuacao:
problemas.append("FALTA_PONTUACAO")
if not tem_tamanho_minimo:
problemas.append("FRASE_MUITO_CURTA")
if tem_padrao_incompleto:
problemas.append("PADRAO_INCOMPLETO")
if comeca_conector and len(palavras) < 4:
problemas.append("INICIO_INCOMPLETO")
return {
"tem_problema": len(problemas) > 0,
"problemas": problemas,
"pontuacao_ok": termina_pontuacao,
"tamanho_ok": tem_tamanho_minimo
}
def validar_tudo(self, texto):
"""Executa todas as validações"""
resultado_ofensas = self.validar_ofensas(texto)
resultado_sentido = self.validar_sentido(texto)
resultado_completude = self.validar_completude(texto)
alertas = []
if resultado_ofensas["tem_ofensa"]:
alertas.append({
"tipo": "🚫 OFENSA",
"mensagem": f"Palavras ofensivas detectadas: {', '.join(resultado_ofensas['palavras'])}",
"nivel": "ALTO",
"cor": "#FF0000"
})
if resultado_sentido["tem_problema"]:
alertas.append({
"tipo": "🤔 SEM SENTIDO",
"mensagem": f"Frase pode não fazer sentido ({resultado_sentido['tipo']})",
"nivel": "MÉDIO",
"cor": "#FF9900"
})
if resultado_completude["tem_problema"]:
problemas_texto = ", ".join(resultado_completude["problemas"])
alertas.append({
"tipo": "📝 INCOMPLETO",
"mensagem": f"Frase incompleta ou mal estruturada: {problemas_texto}",
"nivel": "BAIXO",
"cor": "#FFCC00"
})
return {
"alertas": alertas,
"ofensas": resultado_ofensas,
"sentido": resultado_sentido,
"completude": resultado_completude,
"aprovado": len(alertas) == 0
}
# ====================== SISTEMA DE CLASSIFICAÇÃO ======================
class ClassificadorAreas:
def __init__(self):
self.modelo = None
self.vectorizer = None
self.criar_modelo_padrao()
def criar_modelo_padrao(self):
"""Cria modelo padrão baseado em palavras-chave"""
# Dados de treino para cada área
dados_treino = {
"IP": [
"pesquisa científica", "laboratório", "experimento", "artigo científico",
"publicação", "projeto de pesquisa", "cientista", "dados de pesquisa",
"metodologia", "análise estatística"
],
"FD": [
"direito constitucional", "processo judicial", "advogado", "tribunal",
"lei", "jurisprudência", "contrato", "ação judicial", "penal", "civil"
],
"FE": [
"educação infantil", "professor", "pedagogia", "ensino fundamental",
"currículo escolar", "didática", "aprendizagem", "avaliação", "alfabetização"
],
"Reitoria": [
"administração universitária", "reitor", "decisão institucional",
"gestão acadêmica", "políticas da universidade", "coordenação geral",
"orçamento universitário", "planejamento institucional"
]
}
textos = []
labels = []
for area, frases in dados_treino.items():
for frase in frases:
textos.append(frase)
labels.append(area)
self.vectorizer = TfidfVectorizer(max_features=100)
X = self.vectorizer.fit_transform(textos)
self.modelo = LogisticRegression()
self.modelo.fit(X, labels)
print("✅ Modelo de classificação de áreas criado com sucesso!")
def classificar_area(self, texto, area_selecionada):
"""Classifica a área baseada no texto"""
try:
X = self.vectorizer.transform([texto])
predicao = self.modelo.predict(X)[0]
probabilidade = np.max(self.modelo.predict_proba(X)[0])
# Se a área predita for diferente da selecionada e tiver boa confiança, alertar
if predicao != area_selecionada and probabilidade > 0.6:
return predicao, probabilidade
else:
return area_selecionada, probabilidade
except Exception as e:
print(f"⚠️ Erro na classificação de área: {e}")
return area_selecionada, 1.0
# ====================== INICIALIZAR SISTEMAS ======================
print("🔄 Inicializando sistemas...")
validador = ValidadorManifestacoes()
classificador = ClassificadorAreas()
print("✅ Sistemas inicializados com sucesso!")
# ====================== FUNÇÕES DA INTERFACE ======================
def processar_manifestacao(tipo, area, mensagem):
"""Processa a manifestação com todas as validações"""
if not mensagem.strip():
return (
"<div style='color: red; padding: 20px; text-align: center;'>⚠️ Digite uma mensagem!</div>",
"",
{},
"AGUARDANDO"
)
# Validar mensagem
validacao = validador.validar_tudo(mensagem)
# Classificar área sugerida
area_sugerida, confianca_area = classificador.classificar_area(mensagem, area)
# Resultado base
resultado = {
"tipo": tipo,
"area_selecionada": area,
"area_sugerida": area_sugerida,
"confianca_area": f"{confianca_area:.1%}",
"mensagem": mensagem,
"data_hora": datetime.now().strftime("%d/%m/%Y %H:%M:%S"),
"validacao": validacao,
"status": "APROVADA" if validacao["aprovado"] else "REQUER REVISÃO"
}
# Formatar alertas para exibição
alertas_html = "<div style='max-height: 300px; overflow-y: auto;'>"
if validacao["aprovado"]:
alertas_html += """
<div style='
background-color: #10B981;
color: white;
padding: 15px;
border-radius: 8px;
margin: 10px 0;
text-align: center;
font-weight: bold;
'>
✅ MENSAGEM APROVADA - PRONTA PARA ENVIO
</div>
"""
else:
for alerta in validacao["alertas"]:
alertas_html += f"""
<div style='
background-color: {alerta["cor"]}20;
border-left: 5px solid {alerta["cor"]};
padding: 12px;
margin: 8px 0;
border-radius: 5px;
'>
<strong style='color: {alerta["cor"]};'>{alerta["tipo"]} ({alerta["nivel"]}):</strong><br>
{alerta["mensagem"]}
</div>
"""
alertas_html += "</div>"
# Detalhes da validação
detalhes_html = f"""
<div style='margin-top: 20px; padding: 15px; background-color: #f8f9fa; border-radius: 8px;'>
<h4 style='margin-top: 0;'>📋 Detalhes da Validação:</h4>
<p><strong>🔍 Palavras ofensivas:</strong> {len(validacao['ofensas']['palavras'])} encontradas</p>
<p><strong>🧠 Faz sentido:</strong> {'✅ Sim' if not validacao['sentido']['tem_problema'] else '❌ Não'} ({validacao['sentido'].get('confianca', 0.5):.1%})</p>
<p><strong>📝 Frase completa:</strong> {'✅ Sim' if not validacao['completude']['tem_problema'] else '❌ Não'}</p>
<p><strong>🏛️ Área sugerida pelo sistema:</strong> {area_sugerida} ({confianca_area:.1%} de confiança)</p>
<p><strong>📊 Status final:</strong> <span style='color: {'#10B981' if validacao['aprovado'] else '#F59E0B'}; font-weight: bold;'>{resultado['status']}</span></p>
</div>
"""
# JSON para download/armazenamento
json_resultado = {
"manifestacao": {
"tipo": tipo,
"area_original": area,
"area_sugerida": area_sugerida,
"mensagem": mensagem,
"data_hora": resultado["data_hora"],
"status": resultado["status"]
},
"validacoes": {
"ofensas": validacao["ofensas"],
"sentido": validacao["sentido"],
"completude": validacao["completude"]
}
}
return alertas_html, detalhes_html, json_resultado, resultado["status"]
def atualizar_descricao(area):
"""Atualiza descrição da área selecionada"""
return f"**Descrição:** {AREAS.get(area, 'Área não encontrada')}"
def atualizar_contador(texto):
"""Atualiza contador de caracteres"""
palavras = len(texto.split())
return f"<div style='text-align: right; color: #666; font-size: 14px;'>📝 Caracteres: {len(texto)} | 📊 Palavras: {palavras}</div>"
# ====================== INTERFACE GRADIO ======================
with gr.Blocks(title="📋 Sistema de Manifestações Universitárias") as app:
# CSS customizado
app.css = """
.gradio-container {
max-width: 1200px !important;
margin: 0 auto !important;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.header-container {
padding: 25px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 10px;
margin-bottom: 25px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.header-title {
color: white !important;
text-align: center;
margin-bottom: 10px !important;
font-size: 2.2em !important;
font-weight: 700 !important;
}
.header-subtitle {
color: rgba(255,255,255,0.9);
text-align: center;
margin-bottom: 0;
font-size: 1.1em;
}
.section-title {
background: linear-gradient(90deg, #f0f4ff, #e6f7ff);
padding: 12px 20px;
border-radius: 8px;
margin: 20px 0 15px 0;
border-left: 5px solid #667eea;
}
.example-btn {
margin: 5px !important;
}
.status-aprovado {
color: #10B981 !important;
font-weight: bold !important;
}
.status-revisao {
color: #F59E0B !important;
font-weight: bold !important;
}
"""
# Cabeçalho
gr.HTML("""
<div class="header-container">
<h1 class="header-title">🏛️ Sistema de Manifestações Universitárias</h1>
<p class="header-subtitle">Registre reclamações ou sugestões para as áreas da universidade</p>
</div>
""")
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### 1️⃣ Tipo de Manifestação")
tipo_input = gr.Dropdown(
choices=TIPO_MANIFESTACAO,
label="Selecione o tipo",
value="Reclamação",
interactive=True
)
gr.Markdown("### 2️⃣ Área Destino")
area_input = gr.Dropdown(
choices=list(AREAS.keys()),
label="Selecione a área",
value="Reitoria",
interactive=True
)
# Mostrar descrição da área
area_desc = gr.Markdown(f"**Descrição:** {AREAS['Reitoria']}")
area_input.change(atualizar_descricao, inputs=area_input, outputs=area_desc)
gr.Markdown("---")
gr.Markdown("### 📊 Estatísticas")
contador_total = gr.Textbox("Total de manifestações: 0", label="", interactive=False)
with gr.Column(scale=2):
gr.Markdown("### 3️⃣ Escreva sua Manifestação")
mensagem_input = gr.Textbox(
label="Descreva detalhadamente sua reclamação ou sugestão:",
placeholder="Ex: Gostaria de sugerir a instalação de mais bebedouros no prédio da Faculdade de Educação, pois os existentes são insuficientes para a quantidade de alunos, especialmente nos horários de pico.",
lines=8,
interactive=True
)
# Contador de caracteres
contador_chars = gr.HTML("<div style='text-align: right; color: #666; font-size: 14px;'>📝 Caracteres: 0 | 📊 Palavras: 0</div>")
mensagem_input.change(atualizar_contador, inputs=mensagem_input, outputs=contador_chars)
with gr.Row():
btn_enviar = gr.Button("📤 Enviar Manifestação", variant="primary", size="lg")
btn_limpar = gr.Button("🗑️ Limpar Tudo", variant="secondary")
# Resultados
with gr.Row():
with gr.Column():
gr.Markdown("### ⚠️ Alertas de Validação")
alertas_output = gr.HTML(label="", elem_classes=["alertas-container"])
with gr.Column():
gr.Markdown("### 📊 Resultado da Análise")
detalhes_output = gr.HTML(label="", elem_classes=["detalhes-container"])
status_output = gr.Textbox(label="📈 Status Final", interactive=False)
# JSON para download
json_output = gr.JSON(label="📁 Dados Completos (para download)")
# Histórico - CORRIGIDO: removido parâmetro 'height'
with gr.Accordion("📜 Histórico de Manifestações (últimas 10)", open=False):
historico_df = gr.Dataframe(
headers=["Data", "Tipo", "Área", "Status", "Preview"],
value=[],
interactive=False
)
# Estado para histórico
historico_state = gr.State([])
# Processamento completo
def processar_e_armazenar(tipo, area, mensagem, historico):
"""Processa e armazena a manifestação"""
alertas, detalhes, json_data, status = processar_manifestacao(tipo, area, mensagem)
# Adicionar ao histórico
nova_entrada = [
datetime.now().strftime("%d/%m %H:%M"),
tipo,
area,
status,
mensagem[:50] + ("..." if len(mensagem) > 50 else "")
]
historico.append(nova_entrada)
# Atualizar contador
contador_atualizado = f"Total de manifestações: {len(historico)}"
return (
alertas,
detalhes,
json_data,
status,
historico,
pd.DataFrame(historico[-10:]),
contador_atualizado
)
# Limpar tudo
def limpar_tudo():
return ["", "", {}, "", [], pd.DataFrame([]), "Total de manifestações: 0"]
# Conectar eventos
btn_enviar.click(
fn=processar_e_armazenar,
inputs=[tipo_input, area_input, mensagem_input, historico_state],
outputs=[alertas_output, detalhes_output, json_output, status_output, historico_state, historico_df, contador_total]
)
btn_limpar.click(
fn=limpar_tudo,
inputs=None,
outputs=[mensagem_input, alertas_output, detalhes_output, json_output, status_output, historico_state, historico_df, contador_total]
)
# Exemplos pré-definidos
gr.Markdown("### 💡 Exemplos Rápidos (clique para carregar)")
with gr.Row():
exemplo1 = gr.Button("📡 Internet lenta (IP)", size="sm", elem_classes=["example-btn"])
exemplo2 = gr.Button("🚰 Mais bebedouros (FE)", size="sm", elem_classes=["example-btn"])
exemplo3 = gr.Button("🪑 Cadeiras quebradas (FD)", size="sm", elem_classes=["example-btn"])
exemplo4 = gr.Button("⚠️ Exemplo com problema", size="sm", elem_classes=["example-btn"])
def carregar_exemplo1():
return "Reclamação", "IP", "A internet do laboratório de pesquisas está extremamente lenta, impossibilitando o acesso aos artigos científicos. Isso está atrasando nossas pesquisas significativamente. Precisamos de uma solução urgente!"
def carregar_exemplo2():
return "Sugestão", "FE", "Sugiro a instalação de mais bebedouros nos corredores da Faculdade de Educação, pois os existentes são insuficientes para a quantidade de alunos, especialmente nos horários de pico entre as aulas."
def carregar_exemplo3():
return "Reclamação", "FD", "As cadeiras da sala 205 estão quebradas há mais de um mês e ninguém fez a manutenção. Isso compromete o conforto e a segurança dos alunos durante as aulas."
def carregar_exemplo4():
return "Reclamação", "Reitoria", "Isso é uma vergonha! Incompetentes! asdfgh nada funciona direito nessa universidade."
exemplo1.click(carregar_exemplo1, outputs=[tipo_input, area_input, mensagem_input])
exemplo2.click(carregar_exemplo2, outputs=[tipo_input, area_input, mensagem_input])
exemplo3.click(carregar_exemplo3, outputs=[tipo_input, area_input, mensagem_input])
exemplo4.click(carregar_exemplo4, outputs=[tipo_input, area_input, mensagem_input])
# Rodapé informativo
gr.Markdown("---")
gr.Markdown("""
<div style='background-color: #f8f9fa; padding: 20px; border-radius: 10px;'>
<h3 style='margin-top: 0; color: #333;'>ℹ️ Como funciona o sistema:</h3>
<div style='display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; margin-top: 15px;'>
<div style='background: white; padding: 15px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.05);'>
<h4 style='color: #667eea; margin-top: 0;'>✅ Validações automáticas</h4>
<p><strong>🚫 Detecção de ofensas:</strong> Verifica palavras inadequadas no texto</p>
<p><strong>🤔 Análise de sentido:</strong> Identifica frases sem sentido ou spam</p>
<p><strong>📝 Verificação de completude:</strong> Checa se a mensagem está bem estruturada</p>
</div>
<div style='background: white; padding: 15px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.05);'>
<h4 style='color: #667eea; margin-top: 0;'>🏛️ Classificação inteligente</h4>
<p>Sugere a área mais apropriada baseada no conteúdo</p>
<p>Compara com a área selecionada pelo usuário</p>
<p>Mostra nível de confiança da sugestão</p>
</div>
<div style='background: white; padding: 15px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.05);'>
<h4 style='color: #667eea; margin-top: 0;'>📊 Resultados</h4>
<p>Status claro (APROVADA ou REQUER REVISÃO)</p>
<p>Alertas detalhados com cores por gravidade</p>
<p>Histórico das últimas manifestações</p>
<p>Dados exportáveis em JSON</p>
</div>
</div>
</div>
""")
# ====================== INICIAR APLICAÇÃO ======================
if __name__ == "__main__":
print("🚀 Iniciando aplicação Gradio...")
app.launch(
debug=True,
share=False,
server_name="0.0.0.0",
server_port=7860
)