Spaces:
Paused
Paused
Download app.py from MLMPBR/cmn_ia_agent: direct link, hf CLI and curl.
- Browser
- Download file 10.9 kB
-
https://huggingface.co/spaces/MLMPBR/cmn_ia_agent/resolve/main/app.py
- Command line
-
hf download hf://spaces/MLMPBR/cmn_ia_agent/app.py
-
curl -L -o app.py https://huggingface.co/spaces/MLMPBR/cmn_ia_agent/resolve/main/app.py
10.9 kB
| import streamlit as st | |
| import pandas as pd | |
| from sqlalchemy import create_engine | |
| import google.generativeai as genai | |
| import os | |
| from dotenv import load_dotenv | |
| import plotly.express as px | |
| # Carrega as senhas e configurações do arquivo .env | |
| load_dotenv() | |
| # ========================================== | |
| # CONFIGURAÇÃO DE PÁGINA E CABEÇALHO | |
| # ========================================== | |
| st.set_page_config(page_title="Câmara Municipal de Navegantes", page_icon="🔍", layout="wide") | |
| st.title("🔍 Câmara Municipal de Navegantes - Bem-vindo à Navegantes IA") | |
| # Subtítulo centralizado usando HTML/CSS dentro do Markdown | |
| st.markdown("<h4 style='text-align: center; color: #555555; margin-bottom: 2rem;'>Inteligência Artificial a serviço da cidadania e da transparência</h4>", unsafe_allow_html=True) | |
| # ========================================== | |
| # BARRA LATERAL (SIDEBAR) - CHAT AO LADO | |
| # ========================================== | |
| with st.sidebar: | |
| # 1. Logótipo da Câmara Municipal | |
| logo_path = os.path.join("assets", "logo_cmn.jpg") | |
| if os.path.exists(logo_path): | |
| st.image(logo_path, use_column_width=True) | |
| # 2. Controlos do Chat | |
| st.header("⚙️ Controles") | |
| if st.button("🗑️ Limpar Histórico de Chat", use_container_width=True, type="primary"): | |
| st.session_state.mensagens = [{"role": "assistant", "content": "Olá! O motor de análise está pronto. De qual parlamentar ou secretaria deseja visualizar a execução orçamentária?"}] | |
| st.rerun() | |
| st.divider() | |
| # 3. Seção de Prompts Sugeridos | |
| st.header("💡 Pergunte à IA da CMN") | |
| st.markdown("*Copie e cole estas perguntas no Chat ao lado e observe as respostas:*") | |
| st.info("🏆 **Visão Macro e Ranking**\n\nConsiderando todos os dados da prefeitura, faça um ranking dos 3 parlamentares que possuem o maior volume financeiro de 'saldo parado'. Qual recomendação institucional você daria à Câmara Municipal como um todo para otimizar essa execução junto ao Executivo?") | |
| st.success("🤝 **Plano de Ação e Articulação**\n\nTraga o relatório do Vereador Marcos Guarezi. Como o valor efetivamente pago está zerado, monte um plano de ação diplomático de 3 passos para o gabinete dele articular a execução técnica junto às secretarias competentes da prefeitura.") | |
| st.warning("📊 **Didática e Literacia Financeira**\n\nAnalise os dados do Vereador Wancarlos. Sabendo que o sistema está em implantação e que os ritos demoram, explique de forma didática a diferença entre o que ele tem empenhado e o que já foi pago. O que o gabinete deve observar agora?") | |
| # ========================================== | |
| # 1. INICIALIZAR O GEMINI | |
| # ========================================== | |
| GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY") | |
| genai.configure(api_key=GOOGLE_API_KEY) | |
| instrucoes_auditor = """ | |
| Você é uma Assistente Consultiva de Transparência Pública do município de Navegantes. | |
| Sua missão é traduzir dados orçamentários para o cidadão e para os parlamentares de forma elegante, clara e construtiva. | |
| DIRETRIZES RÍGIDAS DE CONDUTA: | |
| 1. FATOS ACIMA DE TUDO: Confie 100% nos números fornecidos nas tabelas. NUNCA invente dados. | |
| 2. TOM DIPLOMÁTICO: Abandone tons punitivos. Seja encorajador e institucional. Compreenda que a execução orçamentária possui ritos próprios. | |
| 3. MENSAGEM DE CONTEXTO: Inclua sempre uma linha sutil informando que "este sistema de rastreio por Inteligência Artificial está em implantação gradativa, visando auxiliar a transparência e a eficiência municipal". | |
| 4. SUGESTÕES PRÁTICAS: Ofereça caminhos construtivos. Sugira articulações entre os gabinetes e as secretarias. | |
| 5. FORMATAÇÃO: Respostas curtas, estruturadas em tópicos. Evite usar o símbolo de cifrão ($) repetidas vezes colado em números para não quebrar a formatação visual. | |
| """ | |
| modelo = genai.GenerativeModel( | |
| model_name="gemini-2.5-flash", | |
| system_instruction=instrucoes_auditor, | |
| generation_config={"temperature": 0.0} | |
| ) | |
| # ========================================== | |
| # 2. CARREGAR DADOS DAS VIEWS DO GOOGLE CLOUD | |
| # ========================================== | |
| def carregar_dados_banco(): | |
| try: | |
| db_url = os.getenv("DATABASE_URL") | |
| engine = create_engine(db_url) | |
| df_vereadores = pd.read_sql_query('SELECT * FROM vw_auditoria_consolidada', engine) | |
| df_secretarias = pd.read_sql_query('SELECT * FROM vw_auditoria_secretarias', engine) | |
| df_matriz = pd.read_sql_query('SELECT * FROM vw_auditoria_vereador_secretaria', engine) | |
| return df_vereadores, df_secretarias, df_matriz | |
| except Exception as e: | |
| st.error(f"Erro ao conectar ao banco de dados: {e}") | |
| return None, None, None | |
| df_view, df_view_secretarias, df_matriz = carregar_dados_banco() | |
| # ========================================== | |
| # 3. MOTOR DE GRÁFICOS (PLOTLY) | |
| # ========================================== | |
| def renderizar_grafico_vereador(nome_vereador, df_v): | |
| dados = df_v[df_v['vereador'] == nome_vereador] | |
| if not dados.empty: | |
| df_plot = pd.DataFrame({ | |
| 'Fase da Execução': ['1. Destinado (Câmara)', '2. Empenhado', '3. Pago'], | |
| 'Valor (R$)': [ | |
| dados['total_destinado_camara'].values[0], | |
| dados['total_empenhado'].values[0], | |
| dados['total_pago'].values[0] | |
| ] | |
| }) | |
| fig = px.bar( | |
| df_plot, | |
| x='Fase da Execução', | |
| y='Valor (R$)', | |
| color='Fase da Execução', | |
| text_auto='.3s', | |
| title=f"Resumo Financeiro: {nome_vereador}", | |
| color_discrete_sequence=['#3b82f6', '#f59e0b', '#10b981'] | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| def renderizar_grafico_secretarias(df_s): | |
| df_plot_s = df_s.sort_values(by='orcamento_atualizado', ascending=False) | |
| fig = px.bar( | |
| df_plot_s, | |
| x='secretaria', | |
| y='orcamento_atualizado', | |
| color='secretaria', | |
| text_auto='.3s', | |
| title="Ranking Orçamentário por Secretaria/Órgão (Orçamento Atualizado)", | |
| labels={'orcamento_atualizado': 'Valor Atualizado (R$)', 'secretaria': 'Secretaria'} | |
| ) | |
| fig.update_layout(xaxis_tickangle=-45) | |
| st.plotly_chart(fig, use_container_width=True) | |
| # ========================================== | |
| # 4. INTERFACE E LÓGICA DO AGENTE | |
| # ========================================== | |
| if df_view is not None and df_view_secretarias is not None and df_matriz is not None: | |
| if "mensagens" not in st.session_state: | |
| st.session_state.mensagens = [{"role": "assistant", "content": "Olá! O motor de análise está pronto. De qual parlamentar ou secretaria deseja visualizar a execução orçamentária?"}] | |
| for msg in st.session_state.mensagens: | |
| with st.chat_message(msg["role"]): | |
| st.markdown(msg["content"]) | |
| if pergunta := st.chat_input("Ex: Relatório da execução do Vereador Chapecó"): | |
| st.session_state.mensagens.append({"role": "user", "content": pergunta}) | |
| with st.chat_message("user"): | |
| st.markdown(pergunta) | |
| with st.chat_message("assistant"): | |
| termos_secretaria = ['secretaria', 'secretarias', 'ranking', 'por pasta', 'executivo'] | |
| solicitou_grafico_secretarias = any(termo in pergunta.lower() for termo in termos_secretaria) | |
| solicitou_ranking = "ranking" in pergunta.lower() | |
| vereador_alvo = None | |
| palavras_ignoradas = ['vereador', 'vereadora', 'professor', 'delegada', 'dr', 'dra', 'doutor', 'doutora'] | |
| for v in df_view['vereador']: | |
| partes_nome = str(v).lower().split() | |
| partes_uteis = [p for p in partes_nome if p not in palavras_ignoradas and len(p) > 3] | |
| if any(p in pergunta.lower() for p in partes_uteis): | |
| vereador_alvo = v | |
| break | |
| prompt_final = "" | |
| # Prioridade 1: Vereador Específico (Envia o Resumo + O Drill-Down da Matriz) | |
| if vereador_alvo: | |
| renderizar_grafico_vereador(vereador_alvo, df_view) | |
| dados_consolidados = df_view[df_view['vereador'] == vereador_alvo].to_markdown(index=False) | |
| # Filtragem blindada contra espaços e maiúsculas/minúsculas | |
| df_filtrado = df_matriz[df_matriz['vereador'].str.lower().str.strip() == str(vereador_alvo).lower().strip()] | |
| dados_detalhados = df_filtrado.to_markdown(index=False) | |
| prompt_final = f""" | |
| DADOS CONSOLIDADOS DO GABINETE: | |
| {dados_consolidados} | |
| DADOS DETALHADOS (GABINETE x SECRETARIAS): | |
| {dados_detalhados} | |
| PERGUNTA DO USUÁRIO: {pergunta} | |
| """ | |
| # Prioridade 2: Ranking de Secretarias | |
| elif solicitou_grafico_secretarias or solicitou_ranking: | |
| renderizar_grafico_secretarias(df_view_secretarias) | |
| prompt_final = f""" | |
| DADOS POR SECRETARIA (RANKING): | |
| {df_view_secretarias.to_markdown(index=False)} | |
| O usuário solicitou um ranking/gráfico das secretarias municipais. O gráfico já foi gerado. | |
| Redija um parecer comentando este ranking, destacando as pastas com maior Orçamento Atualizado e os maiores saldos parados. | |
| """ | |
| # Prioridade 3: Pergunta Genérica Macro | |
| else: | |
| prompt_final = f""" | |
| DADOS MACRO - POR VEREADOR: | |
| {df_view.to_markdown(index=False)} | |
| DADOS MACRO - POR SECRETARIA/ÓRGÃO: | |
| {df_view_secretarias.to_markdown(index=False)} | |
| MATRIZ COMPLETA DE CRUZAMENTO (VEREADOR x SECRETARIA): | |
| {df_matriz.to_markdown(index=False)} | |
| PERGUNTA DO USUÁRIO: {pergunta} | |
| """ | |
| with st.spinner("A redigir o parecer consultivo..."): | |
| try: | |
| resposta = modelo.generate_content(prompt_final) | |
| st.markdown(resposta.text) | |
| st.session_state.mensagens.append({"role": "assistant", "content": resposta.text}) | |
| except Exception as e: | |
| st.error(f"Erro ao consultar a IA: {e}") |