anonymous12321's picture
Update app.py
ae042fe verified
import gradio as gr
import numpy as np
import joblib
import re
from pathlib import Path
from scipy.sparse import hstack, csr_matrix
try:
import torch
from transformers import AutoTokenizer, AutoModel
TORCH_AVAILABLE = True
except ImportError:
TORCH_AVAILABLE = False
# ---------------- Classifier ----------------
class PortugueseClassifier:
def __init__(self):
self.model_path = Path("models")
self.labels = None
self.models_loaded = False
self.tfidf_vectorizer = None
self.meta_learner = None
self.mlb = None
self.optimal_thresholds = None
self.trained_base_models = None
if TORCH_AVAILABLE:
self.bert_tokenizer = None
self.bert_model = None
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
self.load_models()
def load_models(self):
try:
mlb_path = self.model_path / "int_stacking_mlb_encoder.joblib"
tfidf_path = self.model_path / "int_stacking_tfidf_vectorizer.joblib"
meta_path = self.model_path / "int_stacking_meta_learner.joblib"
thresh_path = self.model_path / "int_stacking_optimal_thresholds.npy"
base_path = self.model_path / "int_stacking_base_models.joblib"
self.mlb = joblib.load(mlb_path)
self.labels = self.mlb.classes_.tolist()
self.tfidf_vectorizer = joblib.load(tfidf_path)
self.meta_learner = joblib.load(meta_path)
self.optimal_thresholds = np.load(thresh_path)
self.trained_base_models = joblib.load(base_path)
if TORCH_AVAILABLE:
self.bert_tokenizer = AutoTokenizer.from_pretrained('neuralmind/bert-base-portuguese-cased')
self.bert_model = AutoModel.from_pretrained('neuralmind/bert-base-portuguese-cased')
self.bert_model.eval()
self.bert_model = self.bert_model.to(self.device)
self.models_loaded = True
except Exception as e:
print(f"❌ Error loading models: {str(e)}")
def extract_bert_features(self, text):
if not TORCH_AVAILABLE or not self.bert_model:
return np.zeros((1, 768))
try:
inputs = self.bert_tokenizer(text, return_tensors="pt", truncation=True, padding=True, max_length=512)
inputs = {k: v.to(self.device) for k, v in inputs.items()}
with torch.no_grad():
outputs = self.bert_model(**inputs)
return outputs.last_hidden_state[:, 0, :].cpu().numpy()
except Exception:
return np.zeros((1, 768))
def predict(self, text):
if not self.models_loaded:
return [{"label": "Error", "probability": 0.0, "confidence": "low"}]
text = re.sub(r'\s+', ' ', text.strip())
if not text:
return [{"label": "Empty text", "probability": 0.0, "confidence": "low"}]
tfidf_features = self.tfidf_vectorizer.transform([text])
bert_features = self.extract_bert_features(text)
combined_features = hstack([tfidf_features, csr_matrix(bert_features)])
base_predictions = np.zeros((1, len(self.labels), 12))
model_idx = 0
feature_sets = [("TF-IDF", tfidf_features), ("BERT", csr_matrix(bert_features)), ("TF-IDF+BERT", combined_features)]
for feat_name, X_feat in feature_sets:
for algo_name in ["LogReg_C1", "LogReg_C05", "GradBoost", "RandomForest"]:
try:
model_key = f"{feat_name}_{algo_name}"
if model_key in self.trained_base_models:
model = self.trained_base_models[model_key]
pred = model.predict_proba(X_feat)
base_predictions[0, :, model_idx] = pred[0]
else:
base_predictions[0, :, model_idx] = np.random.rand(len(self.labels)) * 0.3
except Exception:
base_predictions[0, :, model_idx] = np.random.rand(len(self.labels)) * 0.2
model_idx += 1
meta_features = base_predictions.reshape(1, -1)
meta_pred = self.meta_learner.predict_proba(meta_features)[0]
simple_ensemble = np.mean(base_predictions, axis=2)
final_pred = 0.7 * meta_pred + 0.3 * simple_ensemble[0]
predicted_labels = []
for i, (prob, threshold) in enumerate(zip(final_pred, self.optimal_thresholds)):
if prob > threshold:
confidence = "high" if prob > 0.7 else "medium" if prob > 0.4 else "low"
predicted_labels.append({"label": self.labels[i], "probability": float(prob), "confidence": confidence})
if not predicted_labels:
max_idx = np.argmax(final_pred)
prob = final_pred[max_idx]
confidence = "high" if prob > 0.7 else "medium" if prob > 0.4 else "low"
predicted_labels.append({"label": self.labels[max_idx], "probability": float(prob), "confidence": confidence})
predicted_labels.sort(key=lambda x: x["probability"], reverse=True)
return predicted_labels
# ---------------- Load Classifier ----------------
classifier = PortugueseClassifier()
# ---------------- Suggestions ----------------
suggestions = [
"Pelo Senhor Presidente foi presente a esta reunião a informação n.º º ****** da Secção de Urbanismo e Fiscalização -- Serviço de Obras Particulares. \nPonderado e analisado o assunto o Executivo Municipal deliberou por unanimidade aprovar o projeto de arquitetura relativo ao Processo de obras n.º ***** -- EDIFIC.",
"Pelo Senhor Presidente foram presentes a esta reunião os documentos relativos à assunção de compromissos plurianuais relativos à Loja do Cidadão, em Alandroal que se anexam à presente ata. \nPonderado e analisado o assunto o Executivo Municipal deliberou por maioria, com os votos a favor dos eleitos pelo PS e a abstenção da eleita pelo Nós, Cidadãos, aprovar a assunção de compromissos plurianuais. \nReferiu o Sr. Presidente que o FAM enviou então o parecer necessário para aprovação destes dois pontos e que refere que \"atendendo ao exposto propõe que a Direção Executiva do FAM emita parecer prévio positivo à proposta de modificação ao orçamento municipal de 2024 apresentado pela Câmara Municipal\". ",
"-BALANCETE:\n-Operações orçamentas respeitante ao dia vinte e seis de março do corrente ano, é de um milhão oitocentos e onze mil quinhentos e quinze euros e noventa cêntimos.",
"-APOIO SOLICITADO CEAN-CENTRO EDUCATIVO ALICE NABEIRO-VIAGEM DE FINALISTAS-ANO 2024:\n-Apreciação da informação (registo 5911) dos serviços financeiros, referente ao assunto em epígrafe, que a seguir se transcreve:-“ Na sequência da documentação apresentada pelo CEAN – Centro Educativo Alice Nabeiro no âmbito da realização de uma Viagem de Finalistas a Paris, e após análise dos referidos documentos da mencionada entidade e visto a atividade preponderante ser de índole educativo e social, o valor que sugiro quantifica-se em 500,00 (quinhentos euros) o qual considero razoável para o tipo de atividade referida. O valor sugerido tem cobertura orçamental através da Grande Opção do Plano, 2 232 2011/5044 – Transferências Correntes para Instituições Culturais, cuja dotação global prevista nos Documentos Previsionais para o Exercício 2023 é de 150.000,00 €. Pelo exposto e para efeitos de apreciação por parte de V. Exas., é tudo o que me cabe informar, acerca do assunto mencionado em epígrafe”.\n-A CÂMARA DELIBEROU, POR UNANIMIDADE, ATRIBUIR UM APOIO NO VALOR DE 500,00€ (QUINHENTOS EUROS) AO CEAN-CENTRO EDUCATIVO ALICE NABEIRO, TENDO EM VISTA AJUDAR A CUSTEAR A VIAGEM DE FINALISTA 2024."
"-APOIO SOLICITADO PELA ASSOCIAÇÃO CULTURAL AXPRESS-ARTE - FESTIVAL INTERNACIONAL DE DANÇA:\n-Apreciação da informação (registo 5835) dos serviços financeiros, referente ao assunto em epígrafe, que a seguir se transcreve:-“ Na sequência da documentação apresentada pela Axpress-Arte – Associação Cultural para o exercício 2024, e em face da documentação apresentada pela referida instituição no âmbito da candidatura referida em epigrafe, e após análise dos referidos documentos da mencionada entidade e visto a atividade preponderante ser de índole cultural, o valor que sugiro quantifica-se em 3.000,00 (três mil euros) anuais o qual considero razoável para este tipo de atividade. O valor sugerido tem cobertura orçamental através da Grande Opção do Plano, 2 251 2011/5075 – Transferências Correntes para Instituições Culturais, cuja dotação global prevista nos Documentos Previsionais para o Exercício 2024 é de 90.000,00 €. Sendo o Histórico dos últimos anos de atribuição de apoios para a referida instituição o seguinte:\n\n————————————Quadro de Apoios 2019 – 2023————————————\nAno,Valor em Euros\n2019,0,00\n2020,0,00\n2021,0,00\n2022,3.000,00\n2023,3.000,00\n————————————————————————————————————————————————————— \n-Pelo exposto e para efeitos de apreciação por parte de V. Exas., é tudo o que me cabe informar, acerca do assunto mencionado em epígrafe”.\n-A CÂMARA DELIBEROU, POR UNANIMIDADE, ATRIBUIR UM APOIO NO VALOR DE 3.000,00€ (TRÊS MIL EUROS) À ASSOCIAÇÃO CULTURAL AXPRESS-ARTE, TENDO EM VISTA A REALIZAÇÃO DO FESTIVAL INTERNACIONAL DE DANÇA.",
"Presente declaração, emitida pelo Senhor Presidente da Câmara em 07.junho.2024, para ratificação, constante da plataforma de gestão documental SigmaDoc Web/NIPG: 19890/24_Pendente: 100707.\n\nDocumentos que se dão como inteiramente reproduzidos na presente ata e ficam, para todos os efeitos legais, arquivados em pasta própria existente para o efeito.\n\nA Câmara deliberou, ao abrigo da n.º 3, do artigo 35.º do RJAL, aprovado pela Lei n.º 75/2013, de 12 de setembro, na sua redação atual, ratificar a Declaração emitida pelo Senhor Presidente da Camara em 07.junho.2024, em que o Município da Covilhã pretende ceder apoio logístico, institucional e/ou monetário para o evento \"18.ª Santa Bebiana\", atividade esta levada a cabo pela Casa do Povo do Paul, no âmbito da candidatura ao “Cultura ao Centro 2024, da CCDRC”.",
"3.2. – ICOVI, Infraestruturas e Concessões da Covilhã, EM\n\nFoi presente à Câmara informação constante da plataforma de gestão documental SigmaDoc Web/NIPG: 15508/24_Pendente: 101576, relativa ao ofício da ICOVI – Infraestruturas e Concessões da Covilhã, EM, com a referência 11/23, datado de 02.abril.2024, no qual informa o Resultado Antes de Impostos de 2023 negativo no valor de - 176.266,35€ (cento e setenta e seis mil, duzentos e sessenta e seis euros e trinta e cinco cêntimos) e solicita a cobertura desse resultado.\n\nDocumento que se dá como inteiramente reproduzido na presente ata e fica, para todos os efeitos legais, arquivado em pasta própria existente para o efeito.\n\nO Senhor Vereador Ricardo Miguel Correia Leitão Ferreira da Silva criticou o facto de a empresa somente enviar à Câmara um pedido do valor sem pelo menos enviar o relatório e contas.\n\nNão participou na discussão e votação do presente assunto o Senhor Vereador José Miguel Ribeiro Oliveira, nos termos previstos no n.º 6 do artigo 55.º do Anexo I da Lei nº. 75/2013, de 12 de setembro que aprova o RJAL, conjugado com o artigo 69.º do CPA – Código do Procedimento Administrativo, aprovado pelo Decreto-lei n.º 4/2015, de 7 de janeiro, na nova redação, por exercer as funções de Presidente do Conselho da Administração.\n\nA Câmara deliberou, com o voto contra dos Senhores Vereadores Ricardo Miguel Correia Leitão Ferreira da Silva e Jorge Humberto Martins Simões, tendo em conta que a Empresa Municipal ICOVI – Infraestruturas e Concessões da Covilhã, EM, apresenta um Resultado Antes de Impostos de 2023 negativo no valor de - 176.266,35€ (cento e setenta e seis mil, duzentos e sessenta e seis euros e trinta e cinco cêntimos), nos termos da legislação aplicável, aprovar e efetuar uma transferência financeira no valor de 176.266,35€ (cento e setenta e seis mil, duzentos e sessenta e seis euros e trinta e cinco cêntimos)",
"Presente à Câmara informação, constante da distribuição no sistema informático de gestão documental com a referência EDOC/2022/27808, propondo a ratificação do Aditamento ao Contrato de Comparticipação entre o Instituto da Habitação e da Reabilitação Urbana, I.P. e o Município da Covilhã, destinado à Construção de edifício multifamiliar para 12 Alojamentos de Emergência (BNAUT).\n\nDocumentos que se dão como inteiramente reproduzidos na presente ata e ficam, para todos os efeitos legais, arquivados em pasta própria existente para o efeito.\n\nA Câmara deliberou ratificar o Aditamento ao Contrato de Comparticipação entre o Instituto da Habitação e da Reabilitação Urbana, I.P. e o Município da Covilhã – BNAUT, em que as Partes acordam em proceder à alteração do n.º 1 da Cláusula Terceira, do n.º 1 da Cláusula Quarta e da alínea a) do nº 1 da Cláusula Sexta do Contrato, e que tem por objeto a concessão de um apoio financeiro não reembolsável destinado a financiar a realização do projeto designado por “Construção de edifício multifamiliar para alojamento de emergência na Rua Comendador Gomes Correia n.º 39 a 65”, enquadrado no Aviso n.º 02/CO2-i02/2021, em que a Entidade Beneficiária é a Beneficiária Final, entidade globalmente responsável pela execução do Projeto de investimento ora contratualizado.",
"O Presidente da Câmara apresentou declaração de inexistência de conflitos de interesse relativamente aos procedimentos da ordem do dia da presente reunião, constantes dos pontos 1 a 7 e 9 a 18, que se dá aqui por reproduzida e fica arquivada em pasta anexa ao livro de atas.",
"7. TRÂNSITO – FREGUESIA DE PRAZINS SANTO TIRSO E CORVITE – ALTERAÇÃO DE TRÂNSITO NA TRAVESSA DO CAMPO NOVO - Presente a seguinte proposta: “Por forma a incrementar as condições de segurança e acessibilidade local dos moradores, a Junta de Freguesia submeteu proposta de alteração de trânsito na Travessa do Campo Novo, Freguesia de Prazins Santo Tirso e Corvite, no tramo compreendido entre a Travessa Nova do Campo Novo e a Rua 24 de Junho, aprovada pela Assembleia de Freguesia. Considerando os constrangimentos associados ao reduzido perfil transversal da Travessa do Campo Novo, a alteração potenciará o incremento da segurança rodoviária local, bem como a mitigação da prática de estacionamento em contravenção, submete-se à apreciação Camarária conforme postura anexa.” A postura e as atas dos órgãos executivo e deliberativo da Freguesia dão-se aqui por reproduzidos e ficam arquivados em pasta anexa ao livro de atas. DELIBERADO, POR UNANIMIDADE, SUBMETER À APROVAÇÃO DA ASSEMBLEIA MUNICIPAL.",
"1. Ata da reunião pública do Executivo Municipal de 11 de novembro de 2024.\nAprovada, por unanimidade, pelos presentes na reunião pública do Executivo Municipal de 11 de novembro de 2024.",
]
example_idx = 0
def next_example():
global example_idx
example_idx = (example_idx + 1) % len(suggestions)
return suggestions[example_idx]
def prev_example():
global example_idx
example_idx = (example_idx - 1 + len(suggestions)) % len(suggestions)
return suggestions[example_idx]
def use_suggestion(suggestion):
return suggestion
def classify_display(text):
preds = classifier.predict(text)
if not preds:
return "<div style='color:#777;text-align:center'>No topics detected.</div>"
chips = ""
for p in preds[:10]:
label = p["label"]
prob = p["probability"]
conf = p["confidence"]
color = {"high": "#00ff88", "medium": "#ffd966", "low": "#ff6666"}[conf]
chips += f"<span class='output-chip' style='border-color:{color}80;color:{color}'>{label} ({prob:.0%})</span>"
return f"<div style='display:flex;flex-wrap:wrap;gap:10px;justify-content:center;margin-top:10px'>{chips}</div>"
# ---------------- CSS ----------------
custom_css = """
body { background-color: #0c0c0c; color: #f1f1f1; font-family: 'Inter', sans-serif; }
.gradio-container { background-color: #0c0c0c; color: #f1f1f1; }
h2, h3 { text-align: center; color: #00b4ff; font-weight: 600; }
textarea { background-color: #181818 !important; color: #fff !important; border-radius: 10px !important; border: 1px solid #333 !important; }
button { background-color: #007aff !important; color: white !important; font-weight: 600 !important; border-radius: 8px !important; border: none !important; }
button:hover { background-color: #00aaff !important; }
.output-chip { background-color: #1a1a1a; padding: 5px 12px; border-radius: 8px; font-weight: 500; border: 1px solid #007aff33; }
.suggestion-box { background-color: #112f50; border-radius: 10px; border: 1px solid #1f3c5a; padding: 10px; display: flex; align-items: center; justify-content: center; color: #eee; margin-top: 25px; position: relative; overflow: scroll; } .arrow-btn { width: 25px; height: 25px; font-size: 12px; padding: 0; background: none; border: none; color: #e0f0ff; cursor: pointer; font-weight: bold; }
.arrow-btn:hover { color: #ffffff; transform: scale(1.3); }
.use-btn { background-color:#66b3ff !important; color:#000 !important; font-weight:600 !important; border-radius:6px !important; padding:3px 8px !important; margin-left:5px;}
.use-btn:hover { background-color:#99ccff !important; }
.suggestion-box .prev-btn { position: absolute; top: 5px; left: 5px; }
.suggestion-box .next-btn { position: absolute; top: 5px; right: 5px; }
.suggestion-box .suggestion-text { width: 100%; text-align: center; border:none; background:none; color:#eee; font-weight:500; padding-top:8px; overflow-y: scroll;}
"""
# ---------------- Gradio UI ----------------
with gr.Blocks(css=custom_css, theme="gradio/soft") as demo:
gr.Markdown("## 🏛️ **Council Topics Classifier – PT**")
gr.Markdown("### Insert your text (in portuguese):")
input_text = gr.Textbox(label="", placeholder="Write your text (in portuguese)...", lines=6)
classify_btn = gr.Button("Classify")
output = gr.HTML()
classify_btn.click(fn=classify_display, inputs=input_text, outputs=output)
# Sugestões
prev_btn = gr.Button("⟨", elem_classes="prev-btn arrow-btn")
suggestion_display = gr.Textbox(value=suggestions[0], interactive=False, elem_classes="suggestion-text")
next_btn = gr.Button("⟩", elem_classes="next-btn arrow-btn")
use_btn = gr.Button("Use", elem_classes="use-btn")
prev_btn.click(fn=prev_example, outputs=suggestion_display)
next_btn.click(fn=next_example, outputs=suggestion_display)
use_btn.click(fn=use_suggestion, inputs=suggestion_display, outputs=input_text)
gr.Row([prev_btn, suggestion_display, next_btn, use_btn], elem_id="suggestion-box")
# ---------------- Launch ----------------
if __name__ == "__main__":
demo.launch()