Abimael Torcate Claude commited on
Commit
d54ba19
·
0 Parent(s):

Initial commit: Complete checklist management app with AI reporting

Browse files

Features:
- Multi-page Streamlit application for checklist management
- PostgreSQL integration with time tracking analytics
- Process tracking with 6-month deadline monitoring
- AI-powered analysis and executive reports with OpenAI GPT-4
- PDF export functionality with ReportLab
- Comprehensive dashboard with Plotly visualizations
- Environment variable configuration for Hugging Face Spaces deployment

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

.env.example ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # Configurações para Relatórios com IA
2
+ OPENAI_API_KEY=sua_chave_openai_aqui
.gitignore ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # poetry
98
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102
+ #poetry.lock
103
+
104
+ # pdm
105
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106
+ #pdm.lock
107
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108
+ # in version control.
109
+ # https://pdm.fming.dev/#use-with-ide
110
+ .pdm.toml
111
+
112
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113
+ __pypackages__/
114
+
115
+ # Celery stuff
116
+ celerybeat-schedule
117
+ celerybeat.pid
118
+
119
+ # SageMath parsed files
120
+ *.sage.py
121
+
122
+ # Environments
123
+ .env
124
+ .venv
125
+ env/
126
+ venv/
127
+ ENV/
128
+ env.bak/
129
+ venv.bak/
130
+
131
+ # Spyder project settings
132
+ .spyderproject
133
+ .spyproject
134
+
135
+ # Rope project settings
136
+ .ropeproject
137
+
138
+ # mkdocs documentation
139
+ /site
140
+
141
+ # mypy
142
+ .mypy_cache/
143
+ .dmypy.json
144
+ dmypy.json
145
+
146
+ # Pyre type checker
147
+ .pyre/
148
+
149
+ # pytype static type analyzer
150
+ .pytype/
151
+
152
+ # Cython debug symbols
153
+ cython_debug/
154
+
155
+ # PyCharm
156
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
157
+ # be added to the global gitignore or merged into this project gitignore. For a PyCharm
158
+ # project, it is common to ignore common working directory configurations.
159
+ .idea/
160
+
161
+ # VS Code
162
+ .vscode/
163
+
164
+ # Arquivos de teste e desenvolvimento
165
+ test_*.py
166
+ *_test.py
167
+ tests/
168
+ test/
169
+
170
+ # Arquivos de criação e configuração do banco
171
+ create_tables.sql
172
+ add_numero_processo.sql
173
+ test_db.py
174
+
175
+ # Arquivos de configuração local
176
+ .env.local
177
+ .env.development
178
+ config.ini
179
+
180
+ # Arquivos de backup
181
+ *.bak
182
+ *.backup
183
+ *.tmp
184
+
185
+ # Logs
186
+ *.log
187
+ logs/
188
+
189
+ # Dados temporários
190
+ temp/
191
+ tmp/
192
+
193
+ # OS generated files
194
+ .DS_Store
195
+ .DS_Store?
196
+ ._*
197
+ .Spotlight-V100
198
+ .Trashes
199
+ ehthumbs.db
200
+ Thumbs.db
201
+
202
+ # Specific project files to ignore
203
+ CLAUDE.md
204
+ README_local.md
README.md ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🗂️ Gerenciador de Checklists
2
+
3
+ Aplicativo completo para gerenciamento de checklists com análise de tempo e relatórios inteligentes.
4
+
5
+ ## 🚀 Funcionalidades
6
+
7
+ - ✅ **Criação de Checklists Personalizados**
8
+ - 📊 **Dashboard Geral com Analytics**
9
+ - ⏱️ **Análise de Tempo e Movimentos**
10
+ - 🎯 **Controle de Prazos (6 meses)**
11
+ - 🤖 **Relatórios com IA (GPT-4)**
12
+ - 📄 **Export para PDF**
13
+ - 🔢 **Rastreamento por Número de Processo**
14
+
15
+ ## 🛠️ Tecnologias
16
+
17
+ - **Frontend**: Streamlit
18
+ - **Banco de Dados**: PostgreSQL
19
+ - **IA**: OpenAI GPT-4
20
+ - **Visualização**: Plotly, Matplotlib, Seaborn
21
+ - **PDF**: ReportLab
22
+
23
+ ## 📦 Dependências
24
+
25
+ ```bash
26
+ pip install -r requirements.txt
27
+ ```
28
+
29
+ ## ⚙️ Configuração
30
+
31
+ ### Variáveis de Ambiente
32
+
33
+ O aplicativo utiliza as seguintes variáveis de ambiente:
34
+
35
+ #### Banco de Dados
36
+ - `DB_HOST`: Host do PostgreSQL
37
+ - `DB_PORT`: Porta (padrão: 5432)
38
+ - `DB_NAME`: Nome do banco
39
+ - `DB_USER`: Usuário
40
+ - `DB_PASSWORD`: Senha
41
+
42
+ #### IA (OpenAI)
43
+ - `OPENAI_API_KEY`: Chave da API OpenAI para relatórios
44
+
45
+ ### Para Desenvolvimento Local
46
+
47
+ Crie um arquivo `.env`:
48
+ ```env
49
+ OPENAI_API_KEY=sua_chave_openai_aqui
50
+ DB_HOST=seu_host
51
+ DB_PORT=5432
52
+ DB_NAME=checklist
53
+ DB_USER=usuario
54
+ DB_PASSWORD=senha
55
+ ```
56
+
57
+ ## 🚀 Como Usar
58
+
59
+ 1. **Criar Checklist**: Clique em "➕ Novo Checklist"
60
+ 2. **Visualizar**: Acesse via "📊 Dashboard Geral"
61
+ 3. **Análise**: Use "🤖 Relatórios IA" para insights
62
+ 4. **Monitorar**: Acompanhe prazos e progresso
63
+
64
+ ## 📊 Analytics
65
+
66
+ - Tempo médio por processo
67
+ - Taxa de conclusão
68
+ - Análise de riscos de prazo
69
+ - Identificação de gargalos
70
+ - Relatórios executivos
71
+
72
+ ## 🤖 IA e Relatórios
73
+
74
+ - Análise automática de padrões
75
+ - Identificação de ineficiências
76
+ - Recomendações estratégicas
77
+ - Relatórios executivos em PDF
78
+
79
+ ## 🏗️ Estrutura do Projeto
80
+
81
+ ```
82
+ checklist/
83
+ ├── app.py # Aplicação principal
84
+ ├── pages/ # Páginas do Streamlit
85
+ │ ├── criar_checklist.py # Criação de checklists
86
+ │ ├── dashboard.py # Dashboard individual
87
+ │ ├── dashboard_geral.py # Dashboard geral
88
+ │ ├── analytics.py # Análise de tempo
89
+ │ └── relatorio_ia.py # Relatórios com IA
90
+ ├── utils/
91
+ │ └── database.py # Conexão e operações DB
92
+ ├── requirements.txt # Dependências Python
93
+ └── README.md # Este arquivo
94
+ ```
95
+
96
+ ## 📈 Métricas Importantes
97
+
98
+ - **Prazo Ideal**: 6 meses por processo
99
+ - **Status de Prazo**: NO_PRAZO, EM_RISCO, RISCO_ATRASO, ATRASADO
100
+ - **Análise de Velocidade**: Baseada em interações dos usuários
app.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Aplicativo de Gerenciamento de Checklists"""
2
+
3
+ import streamlit as st
4
+ from utils.database import get_all_checklists, test_connection
5
+
6
+ st.set_page_config(
7
+ page_title="Gerenciador de Checklists",
8
+ page_icon="✅",
9
+ layout="centered",
10
+ initial_sidebar_state="collapsed"
11
+ )
12
+
13
+ def init_session_state():
14
+ if 'current_checklist_id' not in st.session_state:
15
+ st.session_state.current_checklist_id = None
16
+
17
+ def main():
18
+ init_session_state()
19
+
20
+ st.title("🗂️ Gerenciador de Checklists")
21
+ st.markdown("---")
22
+
23
+ # Testar conexão com banco
24
+ if not test_connection():
25
+ st.error("❌ Erro ao conectar com o banco de dados!")
26
+ st.stop()
27
+
28
+ col1, col2, col3 = st.columns([1, 2, 1])
29
+
30
+ with col2:
31
+ st.markdown("### Bem-vindo!")
32
+ st.markdown("Crie e gerencie seus checklists personalizados de forma simples e eficiente.")
33
+
34
+ st.markdown("")
35
+
36
+ col1, col2, col3 = st.columns(3)
37
+ with col1:
38
+ if st.button("➕ Novo Checklist", type="primary", use_container_width=True):
39
+ st.switch_page("pages/criar_checklist.py")
40
+ with col2:
41
+ if st.button("📊 Dashboard Geral", use_container_width=True):
42
+ st.switch_page("pages/dashboard_geral.py")
43
+ with col3:
44
+ if st.button("🤖 Relatórios IA", use_container_width=True):
45
+ st.switch_page("pages/relatorio_ia.py")
46
+
47
+ # Buscar checklists do banco
48
+ try:
49
+ checklists = get_all_checklists()
50
+
51
+ if checklists:
52
+ st.markdown("### Seus Checklists")
53
+ for checklist in checklists:
54
+ col_name, col_process, col_date, col_btn = st.columns([3, 2, 1.5, 1])
55
+ with col_name:
56
+ st.write(f"📋 {checklist['name']}")
57
+ with col_process:
58
+ if checklist['numero_processo']:
59
+ st.caption(f"🔢 {checklist['numero_processo']}")
60
+ else:
61
+ st.caption("🔢 Sem processo")
62
+ with col_date:
63
+ st.caption(checklist['created_at'].strftime("%d/%m/%Y"))
64
+ with col_btn:
65
+ if st.button("Ver", key=f"view_{checklist['id']}"):
66
+ st.session_state.current_checklist_id = checklist['id']
67
+ st.switch_page("pages/dashboard.py")
68
+ else:
69
+ st.info("Você ainda não tem checklists. Clique em 'Novo Checklist' para criar o primeiro!")
70
+
71
+ except Exception as e:
72
+ st.error(f"Erro ao buscar checklists: {str(e)}")
73
+
74
+ if __name__ == "__main__":
75
+ main()
76
+
pages/analytics.py ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Página de análises de tempo e movimentos do checklist"""
2
+
3
+ import streamlit as st
4
+ import pandas as pd
5
+ import plotly.express as px
6
+ import plotly.graph_objects as go
7
+ from utils.database import get_checklist_analytics, get_checklist_with_items
8
+ from datetime import datetime, timedelta
9
+
10
+ st.set_page_config(
11
+ page_title="Análises de Tempo",
12
+ page_icon="📊",
13
+ layout="wide"
14
+ )
15
+
16
+ def format_time(seconds):
17
+ """Formata segundos em formato legível"""
18
+ if seconds is None or seconds == 0:
19
+ return "0s"
20
+
21
+ if seconds < 60:
22
+ return f"{int(seconds)}s"
23
+ elif seconds < 3600:
24
+ minutes = int(seconds // 60)
25
+ secs = int(seconds % 60)
26
+ return f"{minutes}m {secs}s"
27
+ else:
28
+ hours = int(seconds // 3600)
29
+ minutes = int((seconds % 3600) // 60)
30
+ return f"{hours}h {minutes}m"
31
+
32
+ def main():
33
+ if 'current_checklist_id' not in st.session_state or st.session_state.current_checklist_id is None:
34
+ st.error("Nenhum checklist selecionado!")
35
+ if st.button("← Voltar para Início"):
36
+ st.switch_page("app.py")
37
+ return
38
+
39
+ checklist_id = st.session_state.current_checklist_id
40
+
41
+ try:
42
+ # Buscar dados do checklist
43
+ checklist = get_checklist_with_items(checklist_id)
44
+ analytics = get_checklist_analytics(checklist_id)
45
+
46
+ if not checklist:
47
+ st.error("Checklist não encontrado!")
48
+ return
49
+
50
+ # Header
51
+ col1, col2 = st.columns([5, 1])
52
+ with col1:
53
+ st.title(f"📊 Análises: {checklist['name']}")
54
+ if checklist['numero_processo']:
55
+ st.caption(f"🔢 Processo: {checklist['numero_processo']}")
56
+ with col2:
57
+ if st.button("← Voltar"):
58
+ st.switch_page("pages/dashboard.py")
59
+
60
+ st.markdown("---")
61
+
62
+ # Estatísticas gerais
63
+ stats = analytics['stats']
64
+ time_analysis = analytics['time_analysis']
65
+
66
+ if stats['first_interaction'] and stats['last_interaction']:
67
+ total_time = stats['last_interaction'] - stats['first_interaction']
68
+ total_minutes = total_time.total_seconds() / 60
69
+ else:
70
+ total_minutes = 0
71
+
72
+ col1, col2, col3, col4 = st.columns(4)
73
+
74
+ with col1:
75
+ st.metric("Total de Items", stats['total_items'])
76
+ with col2:
77
+ st.metric("Items Concluídos", stats['completed_items'])
78
+ with col3:
79
+ completion_rate = (stats['completed_items'] / stats['total_items']) * 100 if stats['total_items'] > 0 else 0
80
+ st.metric("Taxa de Conclusão", f"{completion_rate:.1f}%")
81
+ with col4:
82
+ st.metric("Tempo Total", format_time(total_minutes * 60))
83
+
84
+ st.markdown("---")
85
+
86
+ # Análise por item
87
+ if time_analysis:
88
+ st.markdown("### 📈 Análise de Tempo por Item")
89
+
90
+ # Criar DataFrame para visualização
91
+ df_time = pd.DataFrame(time_analysis)
92
+
93
+ # Converter colunas para tipos numéricos corretos
94
+ df_time['total_seconds_spent'] = pd.to_numeric(df_time['total_seconds_spent'], errors='coerce').fillna(0)
95
+ df_time['avg_seconds_per_completion'] = pd.to_numeric(df_time['avg_seconds_per_completion'], errors='coerce').fillna(0)
96
+ df_time['times_worked'] = pd.to_numeric(df_time['times_worked'], errors='coerce').fillna(0).astype(int)
97
+
98
+ df_time['tempo_formatado'] = df_time['total_seconds_spent'].apply(lambda x: format_time(x) if x else "0s")
99
+ df_time['tempo_medio_formatado'] = df_time['avg_seconds_per_completion'].apply(lambda x: format_time(x) if x else "0s")
100
+ df_time['vezes_trabalhado'] = df_time['times_worked']
101
+
102
+ # Gráfico de barras - Tempo total por item
103
+ if not df_time.empty and df_time['total_seconds_spent'].sum() > 0:
104
+ fig_time = px.bar(
105
+ df_time.sort_values('total_seconds_spent', ascending=False),
106
+ x='item_text',
107
+ y='total_seconds_spent',
108
+ title="Tempo Total Gasto por Item",
109
+ labels={'total_seconds_spent': 'Tempo (segundos)', 'item_text': 'Item'}
110
+ )
111
+ fig_time.update_layout(xaxis_tickangle=-45)
112
+ st.plotly_chart(fig_time, use_container_width=True)
113
+
114
+ # Gráfico de pizza - Distribuição do tempo
115
+ df_positive = df_time[df_time['total_seconds_spent'] > 0]
116
+ if not df_positive.empty:
117
+ fig_pie = px.pie(
118
+ df_positive,
119
+ values='total_seconds_spent',
120
+ names='item_text',
121
+ title="Distribuição do Tempo por Item"
122
+ )
123
+ st.plotly_chart(fig_pie, use_container_width=True)
124
+
125
+ # Tabela detalhada
126
+ st.markdown("### 📋 Detalhes por Item")
127
+
128
+ df_display = df_time[['item_text', 'vezes_trabalhado', 'tempo_formatado', 'tempo_medio_formatado']].copy()
129
+ df_display.columns = ['Item', 'Vezes Trabalhado', 'Tempo Total', 'Tempo Médio']
130
+
131
+ # Ordenar pela coluna numérica original
132
+ df_display = df_display.loc[df_time.sort_values('total_seconds_spent', ascending=False).index]
133
+
134
+ st.dataframe(df_display, use_container_width=True)
135
+
136
+ # Insights automáticos
137
+ st.markdown("### 🔍 Insights")
138
+
139
+ col1, col2 = st.columns(2)
140
+
141
+ with col1:
142
+ st.markdown("#### Items mais demorados")
143
+ if not df_time.empty and df_time['total_seconds_spent'].sum() > 0:
144
+ top_slow = df_time.sort_values('total_seconds_spent', ascending=False).head(3)
145
+ for _, item in top_slow.iterrows():
146
+ if item['total_seconds_spent'] > 0:
147
+ st.write(f"• **{item['item_text']}**: {item['tempo_formatado']}")
148
+ else:
149
+ st.info("Nenhum dado de tempo disponível ainda.")
150
+
151
+ with col2:
152
+ st.markdown("#### Items mais retrabalhados")
153
+ if not df_time.empty and df_time['times_worked'].sum() > 0:
154
+ top_rework = df_time.sort_values('times_worked', ascending=False).head(3)
155
+ for _, item in top_rework.iterrows():
156
+ if item['times_worked'] > 1:
157
+ st.write(f"• **{item['item_text']}**: {item['times_worked']} vezes")
158
+ else:
159
+ st.info("Nenhum retrabalho identificado.")
160
+
161
+ # Recomendações
162
+ st.markdown("#### 💡 Recomendações")
163
+
164
+ if not df_time.empty and df_time['total_seconds_spent'].sum() > 0:
165
+ mean_time = df_time['total_seconds_spent'].mean()
166
+ slow_items = df_time[df_time['total_seconds_spent'] > mean_time]['item_text'].tolist()
167
+ rework_items = df_time[df_time['times_worked'] > 1]['item_text'].tolist()
168
+
169
+ if slow_items:
170
+ st.warning(f"**Items que demandam mais tempo:** {', '.join(slow_items[:3])}")
171
+ st.write("💡 Considere revisar estes items ou dividir em subtarefas menores.")
172
+
173
+ if rework_items:
174
+ st.info(f"**Items com retrabalho:** {', '.join(rework_items[:3])}")
175
+ st.write("💡 Analise se estes items precisam de mais clareza ou recursos adicionais.")
176
+
177
+ if not slow_items and not rework_items:
178
+ st.success("✅ Ótimo trabalho! O checklist está sendo executado de forma eficiente.")
179
+ elif not df_time.empty:
180
+ st.info("📊 Marque e desmarque alguns items para gerar recomendações baseadas no tempo de execução.")
181
+
182
+ else:
183
+ st.info("📊 Ainda não há dados suficientes para análise. Continue marcando os items do checklist para gerar insights.")
184
+
185
+ # Mostrar checklist atual
186
+ st.markdown("### 📋 Status Atual dos Items")
187
+ for item in checklist['items']:
188
+ status = "✅" if item['is_checked'] else "⏳"
189
+ st.write(f"{status} {item['text']}")
190
+
191
+ except Exception as e:
192
+ st.error(f"Erro ao carregar análises: {str(e)}")
193
+ if st.button("← Voltar para Dashboard"):
194
+ st.switch_page("pages/dashboard.py")
195
+
196
+ if __name__ == "__main__":
197
+ main()
pages/criar_checklist.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Página para criar novo checklist"""
2
+
3
+ import streamlit as st
4
+ from utils.database import save_checklist as save_checklist_db
5
+
6
+ st.set_page_config(
7
+ page_title="Criar Novo Checklist",
8
+ page_icon="✅",
9
+ layout="centered"
10
+ )
11
+
12
+ def init_form_state():
13
+ if 'form_items' not in st.session_state:
14
+ st.session_state.form_items = []
15
+ if 'item_to_remove' not in st.session_state:
16
+ st.session_state.item_to_remove = None
17
+
18
+ def add_item():
19
+ st.session_state.form_items.append("")
20
+
21
+ def remove_item(index):
22
+ st.session_state.form_items.pop(index)
23
+
24
+ def save_checklist(name, items, numero_processo):
25
+ if not name:
26
+ st.error("Por favor, dê um nome ao seu checklist!")
27
+ return False
28
+
29
+ if not numero_processo:
30
+ st.error("Por favor, informe o número do processo!")
31
+ return False
32
+
33
+ if not items or all(item == "" for item in items):
34
+ st.error("Por favor, adicione pelo menos um item ao checklist!")
35
+ return False
36
+
37
+ try:
38
+ # Salvar no banco de dados
39
+ checklist_id = save_checklist_db(name, items, numero_processo)
40
+ st.session_state.current_checklist_id = checklist_id
41
+
42
+ # Limpar os dados para próximo uso
43
+ st.session_state.form_items = []
44
+ if 'should_clear' not in st.session_state:
45
+ st.session_state.should_clear = False
46
+ st.session_state.should_clear = True
47
+
48
+ return True
49
+ except Exception as e:
50
+ st.error(f"Erro ao salvar checklist: {str(e)}")
51
+ return False
52
+
53
+ def main():
54
+ init_form_state()
55
+
56
+ st.title("📝 Criar Novo Checklist")
57
+
58
+ col1, col2 = st.columns([5, 1])
59
+ with col1:
60
+ st.markdown("---")
61
+ with col2:
62
+ if st.button("← Voltar"):
63
+ st.switch_page("app.py")
64
+
65
+ # Verifica se deve limpar o formulário após salvar
66
+ if 'should_clear' in st.session_state and st.session_state.should_clear:
67
+ st.session_state.should_clear = False
68
+ st.switch_page("pages/dashboard.py")
69
+
70
+ if st.session_state.item_to_remove is not None:
71
+ remove_item(st.session_state.item_to_remove)
72
+ st.session_state.item_to_remove = None
73
+ st.rerun()
74
+
75
+ with st.container():
76
+ col1, col2 = st.columns(2)
77
+
78
+ with col1:
79
+ checklist_name = st.text_input(
80
+ "Nome do Checklist",
81
+ key="checklist_name_input",
82
+ placeholder="Ex: Tarefas do Projeto X"
83
+ )
84
+
85
+ with col2:
86
+ numero_processo = st.text_input(
87
+ "Número do Processo",
88
+ key="numero_processo_input",
89
+ placeholder="Ex: PROC-2024-001"
90
+ )
91
+
92
+ st.markdown("### Items do Checklist")
93
+
94
+ if not st.session_state.form_items:
95
+ st.info("Clique em 'Adicionar Item' para começar a criar seu checklist")
96
+
97
+ current_items = []
98
+ for i, item in enumerate(st.session_state.form_items):
99
+ col1, col2 = st.columns([5, 1])
100
+ with col1:
101
+ item_value = st.text_input(
102
+ f"Item {i+1}",
103
+ value=item,
104
+ key=f"item_{i}",
105
+ placeholder="Digite o item do checklist"
106
+ )
107
+ current_items.append(item_value)
108
+ with col2:
109
+ st.markdown("<br>", unsafe_allow_html=True)
110
+ if st.button("🗑️", key=f"remove_{i}", help="Remover item"):
111
+ st.session_state.item_to_remove = i
112
+ st.rerun()
113
+
114
+ st.markdown("")
115
+
116
+ col1, col2, col3 = st.columns([2, 2, 2])
117
+
118
+ with col1:
119
+ if st.button("➕ Adicionar Item", use_container_width=True):
120
+ add_item()
121
+ st.rerun()
122
+
123
+ with col3:
124
+ if st.button("💾 Salvar Checklist", type="primary", use_container_width=True):
125
+ if save_checklist(checklist_name, current_items, numero_processo):
126
+ st.success("Checklist criado com sucesso!")
127
+ st.balloons()
128
+
129
+ if __name__ == "__main__":
130
+ main()
pages/dashboard.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Página do dashboard para visualizar e interagir com o checklist"""
2
+
3
+ import streamlit as st
4
+ from utils.database import get_checklist_with_items, toggle_item as toggle_item_db
5
+
6
+ st.set_page_config(
7
+ page_title="Dashboard do Checklist",
8
+ page_icon="✅",
9
+ layout="centered"
10
+ )
11
+
12
+ def get_progress(items):
13
+ if not items:
14
+ return 0
15
+ checked_count = sum(1 for item in items if item['is_checked'])
16
+ return (checked_count / len(items)) * 100
17
+
18
+ def main():
19
+ if 'current_checklist_id' not in st.session_state or st.session_state.current_checklist_id is None:
20
+ st.error("Nenhum checklist selecionado!")
21
+ if st.button("← Voltar para Início"):
22
+ st.switch_page("app.py")
23
+ return
24
+
25
+ checklist_id = st.session_state.current_checklist_id
26
+
27
+ try:
28
+ # Buscar checklist do banco
29
+ checklist = get_checklist_with_items(checklist_id)
30
+
31
+ if not checklist:
32
+ st.error("Checklist não encontrado!")
33
+ if st.button("← Voltar para Início"):
34
+ st.switch_page("app.py")
35
+ return
36
+
37
+ col1, col2, col3 = st.columns([4, 1, 1])
38
+ with col1:
39
+ st.title(f"📋 {checklist['name']}")
40
+ if checklist['numero_processo']:
41
+ st.caption(f"🔢 Processo: {checklist['numero_processo']}")
42
+ with col2:
43
+ if st.button("📊 Análises"):
44
+ st.switch_page("pages/analytics.py")
45
+ with col3:
46
+ if st.button("← Voltar"):
47
+ st.switch_page("app.py")
48
+
49
+ st.markdown("---")
50
+
51
+ progress = get_progress(checklist['items'])
52
+
53
+ col1, col2 = st.columns([3, 1])
54
+ with col1:
55
+ st.progress(progress / 100)
56
+ with col2:
57
+ st.metric("Progresso", f"{progress:.0f}%")
58
+
59
+ st.markdown("### Items do Checklist")
60
+
61
+ if not checklist['items']:
62
+ st.info("Este checklist não possui items.")
63
+ else:
64
+ for item in checklist['items']:
65
+ col1, col2 = st.columns([10, 1])
66
+
67
+ with col1:
68
+ current_state = item['is_checked']
69
+ new_state = st.checkbox(
70
+ item['text'],
71
+ value=current_state,
72
+ key=f"check_{item['id']}"
73
+ )
74
+
75
+ # Se o estado mudou, atualizar no banco
76
+ if new_state != current_state:
77
+ try:
78
+ toggle_item_db(item['id'], checklist_id, new_state)
79
+ st.rerun()
80
+ except Exception as e:
81
+ st.error(f"Erro ao atualizar item: {str(e)}")
82
+
83
+ items_total = len(checklist['items'])
84
+ items_checked = sum(1 for item in checklist['items'] if item['is_checked'])
85
+ items_remaining = items_total - items_checked
86
+
87
+ st.markdown("---")
88
+
89
+ col1, col2, col3 = st.columns(3)
90
+
91
+ with col1:
92
+ st.metric("Total de Items", items_total)
93
+ with col2:
94
+ st.metric("Concluídos", items_checked, delta=f"{items_checked}/{items_total}")
95
+ with col3:
96
+ st.metric("Restantes", items_remaining)
97
+
98
+ if progress == 100:
99
+ st.success("🎉 Parabéns! Você completou todo o checklist!")
100
+ st.balloons()
101
+
102
+ except Exception as e:
103
+ st.error(f"Erro ao carregar checklist: {str(e)}")
104
+ if st.button("← Voltar para Início"):
105
+ st.switch_page("app.py")
106
+
107
+ if __name__ == "__main__":
108
+ main()
pages/dashboard_geral.py ADDED
@@ -0,0 +1,501 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Dashboard Geral - Análise de todos os checklists"""
2
+
3
+ import streamlit as st
4
+ import pandas as pd
5
+ import plotly.express as px
6
+ import plotly.graph_objects as go
7
+ from datetime import datetime, timedelta
8
+ from utils.database import (
9
+ get_all_checklists_with_stats,
10
+ get_general_stats,
11
+ get_checklist_interactions_summary,
12
+ get_process_summary,
13
+ get_fastest_processes,
14
+ get_process_deadline_analysis,
15
+ test_connection
16
+ )
17
+
18
+ st.set_page_config(
19
+ page_title="Dashboard Geral - Checklists",
20
+ page_icon="📊",
21
+ layout="wide"
22
+ )
23
+
24
+ st.title("📊 Painel Geral - Análise de Todos os Checklists")
25
+
26
+ # Função para carregar dados
27
+ @st.cache_data(ttl=300) # Cache por 5 minutos
28
+ def carregar_dados():
29
+ """Carrega todos os dados necessários"""
30
+ try:
31
+ if not test_connection():
32
+ return None, None, None, None, None, None
33
+
34
+ checklists = get_all_checklists_with_stats()
35
+ stats = get_general_stats()
36
+ interactions = get_checklist_interactions_summary()
37
+ processes = get_process_summary()
38
+ fastest_processes = get_fastest_processes()
39
+ deadline_analysis = get_process_deadline_analysis()
40
+
41
+ return checklists, stats, interactions, processes, fastest_processes, deadline_analysis
42
+ except Exception as e:
43
+ st.error(f"❌ Erro ao carregar dados: {str(e)}")
44
+ return None, None, None, None, None, None
45
+
46
+ # Botão para atualizar dados
47
+ col1, col2 = st.columns([6, 1])
48
+ with col2:
49
+ if st.button("🔄 Atualizar Dados"):
50
+ st.cache_data.clear()
51
+ st.rerun()
52
+
53
+ with col1:
54
+ if st.button("← Voltar para Início"):
55
+ st.switch_page("app.py")
56
+
57
+ # Carrega os dados
58
+ resultado = carregar_dados()
59
+ if resultado is None or len(resultado) != 6:
60
+ st.error("❌ Erro ao conectar com o banco de dados ou dados não disponíveis!")
61
+ st.stop()
62
+
63
+ checklists, stats, interactions, processes, fastest_processes, deadline_analysis = resultado
64
+
65
+ if not checklists or not stats:
66
+ st.error("❌ Erro ao conectar com o banco de dados ou dados não disponíveis!")
67
+ st.stop()
68
+
69
+ # Converte para DataFrame
70
+ df_checklists = pd.DataFrame(checklists)
71
+ df_interactions = pd.DataFrame(interactions) if interactions else pd.DataFrame()
72
+ df_processes = pd.DataFrame(processes) if processes else pd.DataFrame()
73
+ df_fastest = pd.DataFrame(fastest_processes) if fastest_processes else pd.DataFrame()
74
+ df_deadlines = pd.DataFrame(deadline_analysis) if deadline_analysis else pd.DataFrame()
75
+
76
+ # Converter colunas numéricas para tipos corretos
77
+ if not df_checklists.empty:
78
+ df_checklists['progress_percentage'] = pd.to_numeric(df_checklists['progress_percentage'], errors='coerce').fillna(0)
79
+ df_checklists['total_items'] = pd.to_numeric(df_checklists['total_items'], errors='coerce').fillna(0)
80
+ df_checklists['completed_items'] = pd.to_numeric(df_checklists['completed_items'], errors='coerce').fillna(0)
81
+
82
+ if not df_interactions.empty:
83
+ df_interactions['total_interactions'] = pd.to_numeric(df_interactions['total_interactions'], errors='coerce').fillna(0)
84
+
85
+ if not df_processes.empty:
86
+ df_processes['avg_progress'] = pd.to_numeric(df_processes['avg_progress'], errors='coerce').fillna(0)
87
+ df_processes['total_checklists'] = pd.to_numeric(df_processes['total_checklists'], errors='coerce').fillna(0)
88
+ df_processes['total_items'] = pd.to_numeric(df_processes['total_items'], errors='coerce').fillna(0)
89
+ df_processes['completed_items'] = pd.to_numeric(df_processes['completed_items'], errors='coerce').fillna(0)
90
+
91
+ if not df_fastest.empty:
92
+ df_fastest['avg_progress'] = pd.to_numeric(df_fastest['avg_progress'], errors='coerce').fillna(0)
93
+ df_fastest['total_checklists'] = pd.to_numeric(df_fastest['total_checklists'], errors='coerce').fillna(0)
94
+ df_fastest['avg_completion_hours'] = pd.to_numeric(df_fastest['avg_completion_hours'], errors='coerce').fillna(0)
95
+ df_fastest['avg_start_hours'] = pd.to_numeric(df_fastest['avg_start_hours'], errors='coerce').fillna(0)
96
+ df_fastest['total_avg_time_hours'] = pd.to_numeric(df_fastest['total_avg_time_hours'], errors='coerce').fillna(0)
97
+
98
+ if not df_deadlines.empty:
99
+ df_deadlines['avg_progress'] = pd.to_numeric(df_deadlines['avg_progress'], errors='coerce').fillna(0)
100
+ df_deadlines['days_elapsed'] = pd.to_numeric(df_deadlines['days_elapsed'], errors='coerce').fillna(0)
101
+ df_deadlines['days_remaining'] = pd.to_numeric(df_deadlines['days_remaining'], errors='coerce').fillna(0)
102
+ df_deadlines['projected_days_to_complete'] = pd.to_numeric(df_deadlines['projected_days_to_complete'], errors='coerce').fillna(0)
103
+
104
+ if df_checklists.empty:
105
+ st.warning("📭 Nenhum checklist encontrado no banco de dados.")
106
+ st.info("💡 Clique em 'Novo Checklist' na página inicial para criar o primeiro checklist.")
107
+ else:
108
+ # =================== MÉTRICA DE PRAZO IDEAL ===================
109
+ st.markdown("---")
110
+ st.info("⏰ **Tempo Médio Ideal de Execução do Processo: 6 meses (180 dias)**")
111
+
112
+ # Estatísticas de prazo
113
+ if not df_deadlines.empty:
114
+ status_counts = df_deadlines['status_prazo'].value_counts()
115
+
116
+ col1, col2, col3, col4, col5 = st.columns(5)
117
+
118
+ with col1:
119
+ concluidos = status_counts.get('CONCLUIDO', 0)
120
+ st.metric("✅ Concluídos", concluidos)
121
+
122
+ with col2:
123
+ no_prazo = status_counts.get('NO_PRAZO', 0)
124
+ st.metric("🟢 No Prazo", no_prazo)
125
+
126
+ with col3:
127
+ em_risco = status_counts.get('EM_RISCO', 0)
128
+ st.metric("🟡 Em Risco", em_risco)
129
+
130
+ with col4:
131
+ risco_atraso = status_counts.get('RISCO_ATRASO', 0)
132
+ st.metric("🟠 Alto Risco", risco_atraso)
133
+
134
+ with col5:
135
+ atrasados = status_counts.get('ATRASADO', 0)
136
+ st.metric("🔴 Atrasados", atrasados)
137
+ # =================== MÉTRICAS GERAIS ===================
138
+ st.markdown("---")
139
+ st.subheader("📈 Métricas Gerais")
140
+
141
+ col1, col2, col3, col4, col5 = st.columns(5)
142
+
143
+ with col1:
144
+ st.metric("🗂️ Total de Checklists", stats['total_checklists'])
145
+
146
+ with col2:
147
+ st.metric("🔢 Processos Únicos", stats['total_processos'] or 0)
148
+
149
+ with col3:
150
+ completed_checklists = stats['completed_checklists'] or 0
151
+ completion_rate = (completed_checklists / stats['total_checklists']) * 100 if stats['total_checklists'] > 0 else 0
152
+ st.metric("✅ Checklists Concluídos", f"{completed_checklists} ({completion_rate:.1f}%)")
153
+
154
+ with col4:
155
+ total_items = stats['total_items'] or 0
156
+ completed_items = stats['completed_items'] or 0
157
+ items_completion = (completed_items / total_items) * 100 if total_items > 0 else 0
158
+ st.metric("📋 Items Concluídos", f"{completed_items}/{total_items} ({items_completion:.1f}%)")
159
+
160
+ with col5:
161
+ avg_progress = stats['avg_progress'] or 0
162
+ st.metric("📊 Progresso Médio", f"{avg_progress:.1f}%")
163
+
164
+ # =================== GRÁFICOS - LINHA 1 ===================
165
+ st.markdown("---")
166
+ st.subheader("📊 Análise por Status e Processo")
167
+
168
+ col1, col2 = st.columns(2)
169
+
170
+ with col1:
171
+ # Gráfico de progresso dos checklists
172
+ st.markdown("#### 📈 Distribuição de Progresso")
173
+
174
+ # Criar faixas de progresso
175
+ df_checklists['faixa_progresso'] = pd.cut(
176
+ df_checklists['progress_percentage'],
177
+ bins=[0, 25, 50, 75, 100],
178
+ labels=['0-25%', '26-50%', '51-75%', '76-100%'],
179
+ include_lowest=True
180
+ )
181
+
182
+ progress_counts = df_checklists['faixa_progresso'].value_counts().sort_index()
183
+
184
+ fig_progress = px.pie(
185
+ values=progress_counts.values,
186
+ names=progress_counts.index,
187
+ title="Distribuição de Checklists por Faixa de Progresso",
188
+ color_discrete_sequence=['#ff6b6b', '#feca57', '#48dbfb', '#1dd1a1']
189
+ )
190
+ st.plotly_chart(fig_progress, use_container_width=True)
191
+
192
+ with col2:
193
+ # Gráfico de processos mais rápidos
194
+ st.markdown("#### ⚡ Top 10 Processos Mais Rápidos")
195
+
196
+ if not df_fastest.empty:
197
+ # Função para formatar tempo em horas
198
+ def format_hours(hours):
199
+ if pd.isna(hours) or hours == 0:
200
+ return "0h"
201
+ if hours < 1:
202
+ minutes = int(hours * 60)
203
+ return f"{minutes}min"
204
+ elif hours < 24:
205
+ return f"{hours:.1f}h"
206
+ else:
207
+ days = int(hours // 24)
208
+ remaining_hours = hours % 24
209
+ return f"{days}d {remaining_hours:.1f}h"
210
+
211
+ # Preparar dados para o gráfico
212
+ df_fastest_display = df_fastest.head(10).copy()
213
+ df_fastest_display['tempo_formatado'] = df_fastest_display['total_avg_time_hours'].apply(format_hours)
214
+
215
+ fig_fastest = px.bar(
216
+ df_fastest_display,
217
+ x='numero_processo',
218
+ y='total_avg_time_hours',
219
+ title="Processos Mais Rápidos (Menor Tempo Médio)",
220
+ color='avg_progress',
221
+ color_continuous_scale="RdYlGn",
222
+ hover_data=['tempo_formatado', 'total_checklists']
223
+ )
224
+ fig_fastest.update_layout(
225
+ xaxis_tickangle=45,
226
+ yaxis_title="Tempo Médio (horas)"
227
+ )
228
+ st.plotly_chart(fig_fastest, use_container_width=True)
229
+ else:
230
+ st.info("Dados de tempo não disponíveis ainda. Execute alguns checklists para gerar métricas de velocidade.")
231
+
232
+ # =================== ANÁLISE DE PRAZOS ===================
233
+ if not df_deadlines.empty:
234
+ st.markdown("---")
235
+ st.subheader("🚨 Análise de Cumprimento de Prazos")
236
+
237
+ col1, col2 = st.columns(2)
238
+
239
+ with col1:
240
+ # Gráfico de status de prazos
241
+ st.markdown("#### 📊 Distribuição por Status de Prazo")
242
+
243
+ status_counts = df_deadlines['status_prazo'].value_counts()
244
+
245
+ # Mapeamento de cores por status
246
+ color_map = {
247
+ 'CONCLUIDO': '#1dd1a1', # Verde
248
+ 'NO_PRAZO': '#00d2d3', # Azul claro
249
+ 'EM_RISCO': '#feca57', # Amarelo
250
+ 'RISCO_ATRASO': '#ff9ff3', # Laranja
251
+ 'ATRASADO': '#ff6b6b', # Vermelho
252
+ 'SEM_DADOS': '#c7ecee' # Cinza
253
+ }
254
+
255
+ colors = [color_map.get(status, '#c7ecee') for status in status_counts.index]
256
+
257
+ fig_status = px.pie(
258
+ values=status_counts.values,
259
+ names=status_counts.index,
260
+ title="Status dos Processos em Relação ao Prazo de 6 Meses",
261
+ color_discrete_sequence=colors
262
+ )
263
+ st.plotly_chart(fig_status, use_container_width=True)
264
+
265
+ with col2:
266
+ # Gráfico de processos que não vão finalizar no prazo
267
+ st.markdown("#### 🚨 Processos em Risco ou Atrasados")
268
+
269
+ processos_problema = df_deadlines[df_deadlines['status_prazo'].isin(['RISCO_ATRASO', 'ATRASADO', 'EM_RISCO'])]
270
+
271
+ if not processos_problema.empty:
272
+ # Calcular dias totais previstos
273
+ processos_problema = processos_problema.copy()
274
+ processos_problema['dias_totais_previstos'] = processos_problema['days_elapsed'] + processos_problema['projected_days_to_complete']
275
+
276
+ fig_problema = px.bar(
277
+ processos_problema.head(10),
278
+ x='numero_processo',
279
+ y='dias_totais_previstos',
280
+ color='status_prazo',
281
+ title="Projeção de Dias Totais vs Prazo (180 dias)",
282
+ color_discrete_map={
283
+ 'EM_RISCO': '#feca57',
284
+ 'RISCO_ATRASO': '#ff9ff3',
285
+ 'ATRASADO': '#ff6b6b'
286
+ },
287
+ hover_data=['avg_progress', 'days_remaining']
288
+ )
289
+
290
+ # Adicionar linha de referência do prazo
291
+ fig_problema.add_hline(y=180, line_dash="dash", line_color="red",
292
+ annotation_text="Prazo Limite (180 dias)")
293
+
294
+ fig_problema.update_layout(xaxis_tickangle=45)
295
+ st.plotly_chart(fig_problema, use_container_width=True)
296
+ else:
297
+ st.success("🎉 Todos os processos estão dentro do prazo!")
298
+
299
+ # =================== GRÁFICOS - LINHA 2 ===================
300
+ st.markdown("---")
301
+ st.subheader("📅 Análise Temporal")
302
+
303
+ col1, col2 = st.columns(2)
304
+
305
+ with col1:
306
+ # Checklists criados ao longo do tempo
307
+ st.markdown("#### 📅 Criação de Checklists ao Longo do Tempo")
308
+
309
+ df_checklists['data_criacao'] = pd.to_datetime(df_checklists['created_at']).dt.date
310
+ checklists_diarios = df_checklists.groupby('data_criacao').size().reset_index(name='quantidade')
311
+
312
+ fig_temporal = px.line(
313
+ checklists_diarios,
314
+ x='data_criacao',
315
+ y='quantidade',
316
+ title="Checklists Criados por Dia",
317
+ markers=True
318
+ )
319
+ st.plotly_chart(fig_temporal, use_container_width=True)
320
+
321
+ with col2:
322
+ # Progresso médio por data de criação
323
+ st.markdown("#### 📊 Progresso Médio por Período")
324
+
325
+ progress_temporal = df_checklists.groupby('data_criacao')['progress_percentage'].mean().reset_index()
326
+
327
+ fig_progress_temporal = px.bar(
328
+ progress_temporal,
329
+ x='data_criacao',
330
+ y='progress_percentage',
331
+ title="Progresso Médio dos Checklists por Data de Criação",
332
+ color='progress_percentage',
333
+ color_continuous_scale="RdYlGn"
334
+ )
335
+ st.plotly_chart(fig_progress_temporal, use_container_width=True)
336
+
337
+ # =================== ANÁLISE DE INTERAÇÕES ===================
338
+ if not df_interactions.empty:
339
+ st.markdown("---")
340
+ st.subheader("🔄 Análise de Atividade")
341
+
342
+ col1, col2 = st.columns(2)
343
+
344
+ with col1:
345
+ # Processos mais ativos
346
+ st.markdown("#### 🔥 Processos Mais Ativos")
347
+
348
+ if not df_interactions.empty:
349
+ # Agrupar por processo e somar interações
350
+ process_activity = df_interactions.groupby('numero_processo').agg({
351
+ 'total_interactions': 'sum',
352
+ 'name': 'count' # contando quantos checklists por processo
353
+ }).reset_index()
354
+ process_activity.columns = ['numero_processo', 'total_interactions', 'total_checklists']
355
+ process_activity = process_activity.sort_values('total_interactions', ascending=False).head(10)
356
+
357
+ fig_active = px.bar(
358
+ process_activity,
359
+ x='total_interactions',
360
+ y='numero_processo',
361
+ orientation='h',
362
+ title="Top 10 Processos por Número de Interações",
363
+ color='total_interactions',
364
+ color_continuous_scale="Blues",
365
+ hover_data=['total_checklists']
366
+ )
367
+ st.plotly_chart(fig_active, use_container_width=True)
368
+ else:
369
+ st.info("Nenhum processo com interações disponível.")
370
+
371
+ with col2:
372
+ # Atividade ao longo do tempo
373
+ st.markdown("#### ⏰ Atividade ao Longo do Tempo")
374
+
375
+ df_interactions['data_primeira_interacao'] = pd.to_datetime(df_interactions['first_interaction']).dt.date
376
+ atividade_diaria = df_interactions.groupby('data_primeira_interacao').size().reset_index(name='checklists_iniciados')
377
+
378
+ fig_atividade = px.line(
379
+ atividade_diaria,
380
+ x='data_primeira_interacao',
381
+ y='checklists_iniciados',
382
+ title="Checklists com Primeira Interação por Dia",
383
+ markers=True
384
+ )
385
+ st.plotly_chart(fig_atividade, use_container_width=True)
386
+
387
+ # =================== RANKING DE PROCESSOS ===================
388
+ if not df_processes.empty:
389
+ st.markdown("---")
390
+ st.subheader("🏆 Ranking de Processos")
391
+
392
+ col1, col2 = st.columns(2)
393
+
394
+ with col1:
395
+ # Processos com maior progresso
396
+ st.markdown("#### 🥇 Processos com Maior Progresso")
397
+
398
+ top_progress = df_processes.sort_values('avg_progress', ascending=False).head(5)
399
+
400
+ for i, (idx, row) in enumerate(top_progress.iterrows()):
401
+ progress = row['avg_progress'] or 0
402
+ medal = "🥇" if i == 0 else "🥈" if i == 1 else "🥉" if i == 2 else "🏅"
403
+ st.write(f"{medal} **{row['numero_processo']}** - {progress:.1f}% ({row['total_checklists']} checklists)")
404
+
405
+ with col2:
406
+ # Processos com mais checklists
407
+ st.markdown("#### 📊 Processos com Mais Checklists")
408
+
409
+ top_volume = df_processes.sort_values('total_checklists', ascending=False).head(5)
410
+
411
+ for i, (idx, row) in enumerate(top_volume.iterrows()):
412
+ progress = row['avg_progress'] or 0
413
+ medal = "🥇" if i == 0 else "🥈" if i == 1 else "🥉" if i == 2 else "🏅"
414
+ st.write(f"{medal} **{row['numero_processo']}** - {row['total_checklists']} checklists ({progress:.1f}% progresso)")
415
+
416
+ # =================== TABELA DETALHADA ===================
417
+ st.markdown("---")
418
+ st.subheader("📋 Dados Detalhados")
419
+
420
+ # Filtros
421
+ col1, col2, col3 = st.columns(3)
422
+
423
+ with col1:
424
+ if not df_processes.empty:
425
+ processos_disponiveis = ['Todos'] + df_processes['numero_processo'].tolist()
426
+ processo_selecionado = st.selectbox(
427
+ "Filtrar por Processo:",
428
+ options=processos_disponiveis,
429
+ index=0
430
+ )
431
+ else:
432
+ processo_selecionado = 'Todos'
433
+
434
+ with col2:
435
+ faixa_progresso_selecionada = st.selectbox(
436
+ "Filtrar por Progresso:",
437
+ options=['Todos', '0-25%', '26-50%', '51-75%', '76-100%'],
438
+ index=0
439
+ )
440
+
441
+ with col3:
442
+ # Ordenação
443
+ ordenacao = st.selectbox(
444
+ "Ordenar por:",
445
+ options=['Data de Criação', 'Progresso', 'Nome', 'Número de Items'],
446
+ index=0
447
+ )
448
+
449
+ # Aplicar filtros
450
+ df_filtrado = df_checklists.copy()
451
+
452
+ if processo_selecionado != 'Todos':
453
+ df_filtrado = df_filtrado[df_filtrado['numero_processo'] == processo_selecionado]
454
+
455
+ if faixa_progresso_selecionada != 'Todos':
456
+ df_filtrado = df_filtrado[df_filtrado['faixa_progresso'] == faixa_progresso_selecionada]
457
+
458
+ # Aplicar ordenação
459
+ if ordenacao == 'Data de Criação':
460
+ df_filtrado = df_filtrado.sort_values('created_at', ascending=False)
461
+ elif ordenacao == 'Progresso':
462
+ df_filtrado = df_filtrado.sort_values('progress_percentage', ascending=False)
463
+ elif ordenacao == 'Nome':
464
+ df_filtrado = df_filtrado.sort_values('name')
465
+ elif ordenacao == 'Número de Items':
466
+ df_filtrado = df_filtrado.sort_values('total_items', ascending=False)
467
+
468
+ # Mostrar tabela filtrada
469
+ if not df_filtrado.empty:
470
+ # Preparar dados para exibição
471
+ df_display = df_filtrado[['name', 'numero_processo', 'total_items', 'completed_items', 'progress_percentage', 'created_at']].copy()
472
+ df_display.columns = ['Nome', 'Processo', 'Total Items', 'Items Concluídos', 'Progresso (%)', 'Data Criação']
473
+ df_display['Data Criação'] = pd.to_datetime(df_display['Data Criação']).dt.strftime('%d/%m/%Y')
474
+
475
+ st.dataframe(df_display, use_container_width=True)
476
+
477
+ # Estatísticas dos dados filtrados
478
+ st.markdown("#### 📊 Estatísticas dos Dados Filtrados")
479
+ col1, col2, col3, col4 = st.columns(4)
480
+
481
+ with col1:
482
+ st.metric("Checklists Filtrados", len(df_filtrado))
483
+
484
+ with col2:
485
+ progresso_medio_filtrado = df_filtrado['progress_percentage'].mean()
486
+ st.metric("Progresso Médio", f"{progresso_medio_filtrado:.1f}%")
487
+
488
+ with col3:
489
+ checklists_completos_filtrados = len(df_filtrado[df_filtrado['progress_percentage'] == 100])
490
+ st.metric("Checklists Completos", checklists_completos_filtrados)
491
+
492
+ with col4:
493
+ total_items_filtrados = df_filtrado['total_items'].sum()
494
+ st.metric("Total de Items", total_items_filtrados)
495
+ else:
496
+ st.info("🔍 Nenhum checklist encontrado com os filtros aplicados.")
497
+
498
+ # Rodapé
499
+ st.markdown("---")
500
+ st.markdown("📊 **Dashboard Geral de Checklists** | Atualizado automaticamente a cada 5 minutos")
501
+ st.markdown("💡 *Dica: Use os filtros para análises mais específicas por processo ou faixa de progresso*")
pages/relatorio_ia.py ADDED
@@ -0,0 +1,325 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Página de Relatórios com IA"""
2
+
3
+ import streamlit as st
4
+ import pandas as pd
5
+ from datetime import datetime
6
+ import os
7
+ from io import BytesIO
8
+ import plotly.express as px
9
+ import plotly.graph_objects as go
10
+
11
+ # Tentar importar dependências da IA
12
+ try:
13
+ import openai
14
+ from reportlab.lib.pagesizes import letter, A4
15
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
16
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
17
+ from reportlab.lib.units import inch
18
+ from dotenv import load_dotenv
19
+ AI_AVAILABLE = True
20
+ except ImportError:
21
+ AI_AVAILABLE = False
22
+
23
+ from utils.database import get_comprehensive_analysis_data
24
+
25
+ def load_prompts():
26
+ """Carrega os prompts dos arquivos"""
27
+ prompts = {}
28
+
29
+ try:
30
+ with open('prompt_analise_checklists.txt', 'r', encoding='utf-8') as f:
31
+ prompts['analise'] = f.read()
32
+ except FileNotFoundError:
33
+ prompts['analise'] = "Analise os dados de checklists fornecidos e forneça insights sobre eficiência dos processos."
34
+
35
+ try:
36
+ with open('prompt_relatorio_executivo.txt', 'r', encoding='utf-8') as f:
37
+ prompts['executivo'] = f.read()
38
+ except FileNotFoundError:
39
+ prompts['executivo'] = "Crie um relatório executivo baseado nos dados fornecidos."
40
+
41
+ return prompts
42
+
43
+ def setup_openai():
44
+ """Configura a API da OpenAI"""
45
+ # Tentar carregar .env se existir (desenvolvimento local)
46
+ try:
47
+ load_dotenv()
48
+ except:
49
+ pass
50
+
51
+ # Priorizar variáveis de ambiente do sistema (Hugging Face Spaces)
52
+ api_key = os.getenv('OPENAI_API_KEY')
53
+
54
+ if not api_key:
55
+ return False
56
+
57
+ openai.api_key = api_key
58
+ return True
59
+
60
+ def format_data_for_ai(data):
61
+ """Formata os dados para análise da IA"""
62
+ process_data = data['process_data']
63
+ global_stats = data['global_stats']
64
+
65
+ # Formatação dos dados de processos
66
+ formatted_data = "=== DADOS DOS PROCESSOS ===\n\n"
67
+
68
+ for process in process_data:
69
+ formatted_data += f"Processo: {process['numero_processo']}\n"
70
+ formatted_data += f"- Checklists: {process['total_checklists']}\n"
71
+ formatted_data += f"- Progresso médio: {process['avg_progress_percentage']}%\n"
72
+ formatted_data += f"- Total de itens: {process['total_items']}\n"
73
+ formatted_data += f"- Itens concluídos: {process['completed_items']}\n"
74
+ formatted_data += f"- Data de início: {process['process_start_date']}\n"
75
+ formatted_data += f"- Dias decorridos: {process['days_elapsed']}\n"
76
+ formatted_data += f"- Dias restantes para deadline: {process['days_remaining_to_deadline']}\n"
77
+ formatted_data += f"- Status do prazo: {process['status_prazo']}\n"
78
+ formatted_data += f"- Projeção para conclusão: {process['projected_days_to_complete']} dias\n"
79
+ formatted_data += f"- Total de interações: {process['total_interactions']}\n\n"
80
+
81
+ # Estatísticas globais
82
+ formatted_data += "=== ESTATÍSTICAS GLOBAIS ===\n\n"
83
+ formatted_data += f"Total de checklists: {global_stats['total_checklists_global']}\n"
84
+ formatted_data += f"Total de processos: {global_stats['total_processes']}\n"
85
+ formatted_data += f"Total de itens: {global_stats['total_items_global']}\n"
86
+ formatted_data += f"Itens concluídos: {global_stats['completed_items_global']}\n"
87
+ formatted_data += f"Total de interações: {global_stats['total_interactions_global']}\n"
88
+ formatted_data += f"Primeiro processo: {global_stats['earliest_process']}\n"
89
+ formatted_data += f"Último processo: {global_stats['latest_process']}\n"
90
+
91
+ return formatted_data
92
+
93
+ def generate_ai_analysis(data, prompt_type="analise"):
94
+ """Gera análise usando OpenAI"""
95
+ if not AI_AVAILABLE:
96
+ return "❌ Bibliotecas de IA não estão instaladas. Execute: pip install openai python-dotenv"
97
+
98
+ if not setup_openai():
99
+ return "❌ Chave da API OpenAI não configurada. Crie um arquivo .env com OPENAI_API_KEY=sua_chave"
100
+
101
+ prompts = load_prompts()
102
+ system_prompt = prompts.get(prompt_type, "Analise os dados fornecidos.")
103
+ formatted_data = format_data_for_ai(data)
104
+
105
+ try:
106
+ client = openai.OpenAI()
107
+ response = client.chat.completions.create(
108
+ model="gpt-4",
109
+ messages=[
110
+ {"role": "system", "content": system_prompt},
111
+ {"role": "user", "content": formatted_data}
112
+ ],
113
+ temperature=0.7,
114
+ max_tokens=2000
115
+ )
116
+
117
+ return response.choices[0].message.content
118
+
119
+ except Exception as e:
120
+ return f"❌ Erro ao gerar análise: {str(e)}"
121
+
122
+ def create_pdf_report(analysis_text, report_type="Análise de Checklists"):
123
+ """Cria PDF do relatório"""
124
+ if not AI_AVAILABLE:
125
+ return None
126
+
127
+ buffer = BytesIO()
128
+ doc = SimpleDocTemplate(buffer, pagesize=A4)
129
+ styles = getSampleStyleSheet()
130
+
131
+ # Estilo customizado para título
132
+ title_style = ParagraphStyle(
133
+ 'CustomTitle',
134
+ parent=styles['Heading1'],
135
+ fontSize=16,
136
+ alignment=1, # Centralizado
137
+ spaceAfter=30,
138
+ )
139
+
140
+ # Conteúdo do PDF
141
+ story = []
142
+
143
+ # Título
144
+ title = Paragraph(f"{report_type}", title_style)
145
+ story.append(title)
146
+
147
+ # Data
148
+ date_str = datetime.now().strftime("%d/%m/%Y às %H:%M")
149
+ date_para = Paragraph(f"Gerado em: {date_str}", styles['Normal'])
150
+ story.append(date_para)
151
+ story.append(Spacer(1, 20))
152
+
153
+ # Conteúdo da análise
154
+ # Quebrar o texto em parágrafos
155
+ paragraphs = analysis_text.split('\n\n')
156
+ for para in paragraphs:
157
+ if para.strip():
158
+ # Verificar se é um título (começar com ##)
159
+ if para.strip().startswith('##'):
160
+ para_clean = para.replace('##', '').strip()
161
+ p = Paragraph(para_clean, styles['Heading2'])
162
+ elif para.strip().startswith('- '):
163
+ p = Paragraph(para.strip(), styles['Normal'])
164
+ else:
165
+ p = Paragraph(para.strip(), styles['Normal'])
166
+ story.append(p)
167
+ story.append(Spacer(1, 12))
168
+
169
+ # Gerar PDF
170
+ doc.build(story)
171
+ buffer.seek(0)
172
+
173
+ return buffer
174
+
175
+ def show_data_overview(data):
176
+ """Mostra visão geral dos dados"""
177
+ process_data = data['process_data']
178
+ global_stats = data['global_stats']
179
+
180
+ st.subheader("📊 Visão Geral dos Dados")
181
+
182
+ col1, col2, col3, col4 = st.columns(4)
183
+
184
+ with col1:
185
+ st.metric("Total de Processos", global_stats['total_processes'])
186
+
187
+ with col2:
188
+ st.metric("Total de Checklists", global_stats['total_checklists_global'])
189
+
190
+ with col3:
191
+ st.metric("Total de Itens", global_stats['total_items_global'])
192
+
193
+ with col4:
194
+ if global_stats['total_items_global'] > 0:
195
+ completion_rate = (global_stats['completed_items_global'] / global_stats['total_items_global']) * 100
196
+ st.metric("Taxa de Conclusão", f"{completion_rate:.1f}%")
197
+ else:
198
+ st.metric("Taxa de Conclusão", "0%")
199
+
200
+ # Status dos processos
201
+ if process_data:
202
+ st.subheader("🎯 Status dos Processos por Prazo")
203
+
204
+ status_counts = {}
205
+ for process in process_data:
206
+ status = process['status_prazo']
207
+ status_counts[status] = status_counts.get(status, 0) + 1
208
+
209
+ # Gráfico de pizza para status
210
+ fig = px.pie(
211
+ values=list(status_counts.values()),
212
+ names=list(status_counts.keys()),
213
+ title="Distribuição de Status dos Processos"
214
+ )
215
+ st.plotly_chart(fig, use_container_width=True)
216
+
217
+ # Gráfico de progresso por processo
218
+ st.subheader("📈 Progresso por Processo")
219
+
220
+ df_processes = pd.DataFrame(process_data)
221
+ if not df_processes.empty:
222
+ fig2 = px.bar(
223
+ df_processes,
224
+ x='numero_processo',
225
+ y='avg_progress_percentage',
226
+ title="Progresso Médio por Processo",
227
+ labels={'avg_progress_percentage': 'Progresso (%)', 'numero_processo': 'Número do Processo'}
228
+ )
229
+ fig2.update_layout(xaxis_tickangle=-45)
230
+ st.plotly_chart(fig2, use_container_width=True)
231
+
232
+ def main():
233
+ st.title("🤖 Relatórios com Inteligência Artificial")
234
+ st.markdown("---")
235
+
236
+ if not AI_AVAILABLE:
237
+ st.error("⚠️ **Dependências de IA não instaladas**")
238
+ st.markdown("""
239
+ Para usar esta funcionalidade, instale as dependências:
240
+ ```bash
241
+ pip install openai reportlab python-dotenv
242
+ ```
243
+ """)
244
+ return
245
+
246
+ # Verificar se tem dados
247
+ try:
248
+ data = get_comprehensive_analysis_data()
249
+ if not data['process_data']:
250
+ st.warning("📋 Nenhum processo encontrado. Crie alguns checklists primeiro!")
251
+ return
252
+ except Exception as e:
253
+ st.error(f"❌ Erro ao carregar dados: {str(e)}")
254
+ return
255
+
256
+ # Abas principais
257
+ tab1, tab2, tab3 = st.tabs(["📊 Dados", "🔍 Análise IA", "📋 Relatório Executivo"])
258
+
259
+ with tab1:
260
+ show_data_overview(data)
261
+
262
+ with tab2:
263
+ st.subheader("🔍 Análise Detalhada com IA")
264
+ st.markdown("Esta análise utiliza IA para identificar padrões, gargalos e oportunidades de melhoria.")
265
+
266
+ if st.button("🚀 Gerar Análise com IA", use_container_width=True):
267
+ with st.spinner("🤖 IA analisando os dados..."):
268
+ analysis = generate_ai_analysis(data, "analise")
269
+
270
+ st.markdown("### 📋 Resultado da Análise")
271
+ st.markdown(analysis)
272
+
273
+ # Botão para download PDF
274
+ if "❌" not in analysis:
275
+ pdf_buffer = create_pdf_report(analysis, "Análise Detalhada de Checklists")
276
+ if pdf_buffer:
277
+ st.download_button(
278
+ label="📄 Download PDF da Análise",
279
+ data=pdf_buffer,
280
+ file_name=f"analise_checklists_{datetime.now().strftime('%Y%m%d_%H%M')}.pdf",
281
+ mime="application/pdf",
282
+ use_container_width=True
283
+ )
284
+
285
+ with tab3:
286
+ st.subheader("📋 Relatório Executivo")
287
+ st.markdown("Relatório estratégico para apresentação à alta gestão.")
288
+
289
+ if st.button("📊 Gerar Relatório Executivo", use_container_width=True):
290
+ with st.spinner("📊 Gerando relatório executivo..."):
291
+ executive_report = generate_ai_analysis(data, "executivo")
292
+
293
+ st.markdown("### 🎯 Relatório Executivo")
294
+ st.markdown(executive_report)
295
+
296
+ # Botão para download PDF
297
+ if "❌" not in executive_report:
298
+ pdf_buffer = create_pdf_report(executive_report, "Relatório Executivo - Checklists de Planejamento")
299
+ if pdf_buffer:
300
+ st.download_button(
301
+ label="📄 Download PDF do Relatório",
302
+ data=pdf_buffer,
303
+ file_name=f"relatorio_executivo_{datetime.now().strftime('%Y%m%d_%H%M')}.pdf",
304
+ mime="application/pdf",
305
+ use_container_width=True
306
+ )
307
+
308
+ # Seção de configuração
309
+ st.markdown("---")
310
+ with st.expander("⚙️ Configuração da API"):
311
+ st.markdown("""
312
+ **Para usar a funcionalidade de IA:**
313
+
314
+ 1. Obtenha uma chave da API OpenAI em: https://platform.openai.com/api-keys
315
+ 2. Crie um arquivo `.env` na pasta do projeto
316
+ 3. Adicione a linha: `OPENAI_API_KEY=sua_chave_aqui`
317
+
318
+ **Arquivo .env exemplo:**
319
+ ```
320
+ OPENAI_API_KEY=sk-proj-abcd1234...
321
+ ```
322
+ """)
323
+
324
+ if __name__ == "__main__":
325
+ main()
prompt_analise_checklists.txt ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Você é um especialista em análise de processos de planejamento e gestão de projetos. Sua função é analisar dados de checklists de processos de planejamento e fornecer insights valiosos.
2
+
3
+ CONTEXTO:
4
+ - Os dados representam checklists de processos de planejamento
5
+ - Cada processo tem um número identificador
6
+ - Os checklists contêm items que podem estar concluídos ou pendentes
7
+ - Há registros de interações (quando usuários marcam/desmarcam items)
8
+ - O prazo ideal para conclusão de um processo é de 6 meses (180 dias)
9
+
10
+ INSTRUÇÕES PARA ANÁLISE:
11
+ 1. Analise os padrões de conclusão dos checklists
12
+ 2. Identifique processos com alta eficiência vs baixa eficiência
13
+ 3. Examine o tempo médio gasto em cada processo
14
+ 4. Identifique gargalos e pontos de melhoria
15
+ 5. Analise a aderência aos prazos estabelecidos
16
+ 6. Forneça recomendações específicas e acionáveis
17
+
18
+ FORMATO DA RESPOSTA:
19
+ - Use linguagem profissional mas acessível
20
+ - Organize em seções claras
21
+ - Inclua métricas específicas quando possível
22
+ - Destaque insights mais importantes
23
+ - Termine com recomendações práticas
24
+
25
+ FOQUE EM:
26
+ - Eficiência dos processos
27
+ - Cumprimento de prazos
28
+ - Padrões de comportamento dos usuários
29
+ - Oportunidades de melhoria
30
+ - Riscos identificados
prompt_relatorio_executivo.txt ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Você é um consultor executivo especializado em processos de planejamento. Sua função é criar um relatório executivo completo e estratégico para a alta gestão.
2
+
3
+ CONTEXTO:
4
+ - Este relatório será apresentado para gestores seniores
5
+ - Os dados são sobre checklists de processos de planejamento
6
+ - O prazo ideal é de 6 meses por processo
7
+ - O foco deve ser em decisões estratégicas e ações gerenciais
8
+
9
+ INSTRUÇÕES:
10
+ 1. Crie um relatório executivo conciso mas completo
11
+ 2. Use linguagem executiva apropriada
12
+ 3. Inclua análise de riscos e oportunidades
13
+ 4. Forneça recomendações específicas com priorização
14
+ 5. Destaque métricas chave de performance (KPIs)
15
+ 6. Identifique necessidades de recursos ou ajustes
16
+
17
+ ESTRUTURA OBRIGATÓRIA:
18
+ ## RESUMO EXECUTIVO
19
+ - Principais descobertas em 3-4 pontos
20
+ - Status geral dos processos
21
+
22
+ ## INDICADORES CHAVE
23
+ - Métricas principais de performance
24
+ - Comparação com metas estabelecidas
25
+
26
+ ## ANÁLISE DE RISCOS
27
+ - Processos em risco de atraso
28
+ - Impactos potenciais
29
+
30
+ ## OPORTUNIDADES DE MELHORIA
31
+ - Pontos de otimização identificados
32
+ - Benefícios esperados
33
+
34
+ ## RECOMENDAÇÕES ESTRATÉGICAS
35
+ - Ações imediatas (próximos 30 dias)
36
+ - Ações de médio prazo (próximos 3 meses)
37
+ - Investimentos ou recursos necessários
38
+
39
+ ## CONCLUSÃO
40
+ - Próximos passos
41
+ - Expectativas de resultados
42
+
43
+ DIRETRIZES:
44
+ - Máximo 2 páginas quando impresso
45
+ - Linguagem objetiva e direta
46
+ - Foco em ação e resultados
47
+ - Inclua números e percentuais específicos
48
+ - Evite jargões técnicos desnecessários
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ streamlit==1.47.1
2
+ psycopg2-binary==2.9.10
3
+ plotly==6.2.0
4
+ pandas==2.3.1
5
+ matplotlib==3.10.5
6
+ seaborn==0.13.2
7
+ openai==1.98.0
8
+ reportlab==4.4.3
9
+ python-dotenv==1.1.1
setup_database.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Script para configurar o banco de dados"""
2
+
3
+ from utils.database import test_connection, create_tables
4
+
5
+ def main():
6
+ print("Testando conexão com o banco de dados...")
7
+
8
+ if test_connection():
9
+ print("\nConexão bem-sucedida!")
10
+ print("\nCriando tabelas...")
11
+
12
+ try:
13
+ create_tables()
14
+ print("\nBanco de dados configurado com sucesso!")
15
+ print("\nTabelas criadas:")
16
+ print("- checklists")
17
+ print("- checklist_items")
18
+ print("- item_interactions")
19
+ print("\nViews criadas:")
20
+ print("- current_item_states")
21
+ print("- item_time_analysis")
22
+ except Exception as e:
23
+ print(f"\nErro ao criar tabelas: {e}")
24
+ else:
25
+ print("\nFalha na conexão com o banco de dados.")
26
+
27
+ if __name__ == "__main__":
28
+ main()
update_database.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Script para adicionar coluna numero_processo"""
2
+
3
+ from utils.database import get_db_connection
4
+
5
+ def add_numero_processo_column():
6
+ """Adiciona coluna numero_processo na tabela checklists"""
7
+ with open('/Users/abimaeltorcate/checklist/add_numero_processo.sql', 'r') as f:
8
+ sql_script = f.read()
9
+
10
+ try:
11
+ with get_db_connection() as conn:
12
+ with conn.cursor() as cur:
13
+ # Executar cada comando separadamente
14
+ commands = sql_script.split(';')
15
+ for command in commands:
16
+ command = command.strip()
17
+ if command:
18
+ print(f"Executando: {command[:50]}...")
19
+ cur.execute(command)
20
+
21
+ conn.commit()
22
+ print("✅ Coluna numero_processo adicionada com sucesso!")
23
+
24
+ # Verificar se a coluna foi adicionada
25
+ cur.execute("""
26
+ SELECT column_name, data_type, is_nullable
27
+ FROM information_schema.columns
28
+ WHERE table_name = 'checklists' AND table_schema = 'public'
29
+ ORDER BY ordinal_position
30
+ """)
31
+
32
+ columns = cur.fetchall()
33
+ print("\n📋 Colunas da tabela checklists:")
34
+ for col in columns:
35
+ print(f" - {col[0]} ({col[1]}) - Nullable: {col[2]}")
36
+
37
+ except Exception as e:
38
+ print(f"❌ Erro: {e}")
39
+
40
+ if __name__ == "__main__":
41
+ add_numero_processo_column()
utils/database.py ADDED
@@ -0,0 +1,628 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Módulo de conexão e operações com o banco de dados PostgreSQL"""
2
+
3
+ import psycopg2
4
+ from psycopg2.extras import RealDictCursor
5
+ import uuid
6
+ from datetime import datetime
7
+ from contextlib import contextmanager
8
+ import streamlit as st
9
+ import os
10
+
11
+ # Configurações do banco de dados
12
+ # Prioriza variáveis de ambiente (Hugging Face Spaces) sobre valores hardcoded
13
+ DB_CONFIG = {
14
+ 'host': os.getenv('DB_HOST', '77.37.43.160'),
15
+ 'port': int(os.getenv('DB_PORT', 5432)),
16
+ 'database': os.getenv('DB_NAME', 'checklist'),
17
+ 'user': os.getenv('DB_USER', 'abimael'),
18
+ 'password': os.getenv('DB_PASSWORD', 'ctweek')
19
+ }
20
+
21
+ @contextmanager
22
+ def get_db_connection():
23
+ """Context manager para conexão com o banco de dados"""
24
+ conn = None
25
+ try:
26
+ conn = psycopg2.connect(**DB_CONFIG)
27
+ yield conn
28
+ except Exception as e:
29
+ if conn:
30
+ conn.rollback()
31
+ raise e
32
+ finally:
33
+ if conn:
34
+ conn.close()
35
+
36
+ def create_tables():
37
+ """Cria as tabelas do banco se não existirem"""
38
+ # SQL inline para evitar dependência de arquivo externo
39
+ sql_script = """
40
+ -- Criar tabela de checklists
41
+ CREATE TABLE IF NOT EXISTS checklists (
42
+ id VARCHAR(36) PRIMARY KEY,
43
+ name VARCHAR(255) NOT NULL,
44
+ numero_processo VARCHAR(100),
45
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
46
+ );
47
+
48
+ -- Criar tabela de itens do checklist
49
+ CREATE TABLE IF NOT EXISTS checklist_items (
50
+ id VARCHAR(36) PRIMARY KEY,
51
+ checklist_id VARCHAR(36) REFERENCES checklists(id) ON DELETE CASCADE,
52
+ text TEXT NOT NULL,
53
+ position INTEGER NOT NULL
54
+ );
55
+
56
+ -- Criar tabela de interações
57
+ CREATE TABLE IF NOT EXISTS item_interactions (
58
+ id SERIAL PRIMARY KEY,
59
+ item_id VARCHAR(36) REFERENCES checklist_items(id) ON DELETE CASCADE,
60
+ checklist_id VARCHAR(36) REFERENCES checklists(id) ON DELETE CASCADE,
61
+ action VARCHAR(20) NOT NULL,
62
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
63
+ );
64
+
65
+ -- Criar view para estados atuais dos itens
66
+ CREATE OR REPLACE VIEW current_item_states AS
67
+ WITH latest_interactions AS (
68
+ SELECT
69
+ item_id,
70
+ action,
71
+ timestamp,
72
+ ROW_NUMBER() OVER (PARTITION BY item_id ORDER BY timestamp DESC) as rn
73
+ FROM item_interactions
74
+ )
75
+ SELECT
76
+ item_id,
77
+ CASE WHEN action = 'checked' THEN true ELSE false END as is_checked
78
+ FROM latest_interactions
79
+ WHERE rn = 1;
80
+
81
+ -- Criar view para análise de tempo por item
82
+ CREATE OR REPLACE VIEW item_time_analysis AS
83
+ WITH item_sessions AS (
84
+ SELECT
85
+ ii.item_id,
86
+ ci.text as item_text,
87
+ ci.position,
88
+ ci.checklist_id,
89
+ ii.timestamp,
90
+ ii.action,
91
+ LAG(ii.timestamp) OVER (
92
+ PARTITION BY ii.item_id
93
+ ORDER BY ii.timestamp
94
+ ) as prev_timestamp,
95
+ LAG(ii.action) OVER (
96
+ PARTITION BY ii.item_id
97
+ ORDER BY ii.timestamp
98
+ ) as prev_action
99
+ FROM item_interactions ii
100
+ JOIN checklist_items ci ON ii.item_id = ci.id
101
+ ORDER BY ii.item_id, ii.timestamp
102
+ ),
103
+ work_sessions AS (
104
+ SELECT
105
+ item_id,
106
+ item_text,
107
+ position,
108
+ checklist_id,
109
+ CASE
110
+ WHEN prev_action = 'unchecked' AND action = 'checked' AND prev_timestamp IS NOT NULL
111
+ THEN EXTRACT(EPOCH FROM (timestamp - prev_timestamp))
112
+ ELSE 0
113
+ END as session_seconds
114
+ FROM item_sessions
115
+ WHERE prev_timestamp IS NOT NULL
116
+ )
117
+ SELECT
118
+ item_id,
119
+ item_text,
120
+ position,
121
+ checklist_id,
122
+ COUNT(CASE WHEN session_seconds > 0 THEN 1 END) as times_worked,
123
+ COALESCE(SUM(session_seconds), 0) as total_seconds_spent,
124
+ CASE
125
+ WHEN COUNT(CASE WHEN session_seconds > 0 THEN 1 END) > 0
126
+ THEN COALESCE(SUM(session_seconds), 0) / COUNT(CASE WHEN session_seconds > 0 THEN 1 END)
127
+ ELSE 0
128
+ END as avg_seconds_per_completion
129
+ FROM work_sessions
130
+ GROUP BY item_id, item_text, position, checklist_id;
131
+ """
132
+
133
+ with get_db_connection() as conn:
134
+ with conn.cursor() as cur:
135
+ cur.execute(sql_script)
136
+ conn.commit()
137
+ print("Tabelas criadas com sucesso!")
138
+
139
+ def save_checklist(name, items, numero_processo=None):
140
+ """Salva um novo checklist no banco de dados"""
141
+ checklist_id = str(uuid.uuid4())
142
+
143
+ with get_db_connection() as conn:
144
+ with conn.cursor() as cur:
145
+ # Inserir checklist
146
+ cur.execute("""
147
+ INSERT INTO checklists (id, name, numero_processo)
148
+ VALUES (%s, %s, %s)
149
+ """, (checklist_id, name, numero_processo))
150
+
151
+ # Inserir items
152
+ for position, item_text in enumerate(items):
153
+ if item_text.strip():
154
+ item_id = str(uuid.uuid4())
155
+ cur.execute("""
156
+ INSERT INTO checklist_items (id, checklist_id, text, position)
157
+ VALUES (%s, %s, %s, %s)
158
+ """, (item_id, checklist_id, item_text.strip(), position))
159
+
160
+ conn.commit()
161
+
162
+ return checklist_id
163
+
164
+ def get_all_checklists():
165
+ """Retorna todos os checklists do banco"""
166
+ with get_db_connection() as conn:
167
+ with conn.cursor(cursor_factory=RealDictCursor) as cur:
168
+ cur.execute("""
169
+ SELECT id, name, numero_processo, created_at
170
+ FROM checklists
171
+ ORDER BY created_at DESC
172
+ """)
173
+ return cur.fetchall()
174
+
175
+ def get_checklist_with_items(checklist_id):
176
+ """Retorna um checklist com seus itens e estados atuais"""
177
+ with get_db_connection() as conn:
178
+ with conn.cursor(cursor_factory=RealDictCursor) as cur:
179
+ # Buscar checklist
180
+ cur.execute("""
181
+ SELECT id, name, numero_processo, created_at
182
+ FROM checklists
183
+ WHERE id = %s
184
+ """, (checklist_id,))
185
+ checklist = cur.fetchone()
186
+
187
+ if not checklist:
188
+ return None
189
+
190
+ # Buscar items com estado atual
191
+ cur.execute("""
192
+ SELECT
193
+ ci.id,
194
+ ci.text,
195
+ ci.position,
196
+ COALESCE(cis.is_checked, false) as is_checked
197
+ FROM checklist_items ci
198
+ LEFT JOIN current_item_states cis ON ci.id = cis.item_id
199
+ WHERE ci.checklist_id = %s
200
+ ORDER BY ci.position
201
+ """, (checklist_id,))
202
+
203
+ checklist['items'] = cur.fetchall()
204
+
205
+ return checklist
206
+
207
+ def toggle_item(item_id, checklist_id, new_state):
208
+ """Registra a mudança de estado de um item"""
209
+ action = 'checked' if new_state else 'unchecked'
210
+
211
+ with get_db_connection() as conn:
212
+ with conn.cursor() as cur:
213
+ cur.execute("""
214
+ INSERT INTO item_interactions (item_id, checklist_id, action)
215
+ VALUES (%s, %s, %s)
216
+ """, (item_id, checklist_id, action))
217
+ conn.commit()
218
+
219
+ def get_checklist_analytics(checklist_id):
220
+ """Retorna análises de tempo do checklist"""
221
+ with get_db_connection() as conn:
222
+ with conn.cursor(cursor_factory=RealDictCursor) as cur:
223
+ # Estatísticas gerais
224
+ cur.execute("""
225
+ SELECT
226
+ COUNT(DISTINCT ci.id) as total_items,
227
+ COUNT(DISTINCT CASE WHEN cis.is_checked THEN ci.id END) as completed_items,
228
+ MIN(ii.timestamp) as first_interaction,
229
+ MAX(ii.timestamp) as last_interaction
230
+ FROM checklist_items ci
231
+ LEFT JOIN current_item_states cis ON ci.id = cis.item_id
232
+ LEFT JOIN item_interactions ii ON ci.id = ii.item_id
233
+ WHERE ci.checklist_id = %s
234
+ """, (checklist_id,))
235
+
236
+ stats = cur.fetchone()
237
+
238
+ # Análise de tempo por item
239
+ cur.execute("""
240
+ SELECT
241
+ item_text,
242
+ position,
243
+ times_worked,
244
+ total_seconds_spent,
245
+ avg_seconds_per_completion
246
+ FROM item_time_analysis
247
+ WHERE checklist_id = %s
248
+ ORDER BY position
249
+ """, (checklist_id,))
250
+
251
+ time_analysis = cur.fetchall()
252
+
253
+ return {
254
+ 'stats': stats,
255
+ 'time_analysis': time_analysis
256
+ }
257
+
258
+ def delete_checklist(checklist_id):
259
+ """Deleta um checklist e todos os seus dados relacionados"""
260
+ with get_db_connection() as conn:
261
+ with conn.cursor() as cur:
262
+ cur.execute("DELETE FROM checklists WHERE id = %s", (checklist_id,))
263
+ conn.commit()
264
+
265
+ def get_all_checklists_with_stats():
266
+ """Retorna todos os checklists com estatísticas de progresso"""
267
+ with get_db_connection() as conn:
268
+ with conn.cursor(cursor_factory=RealDictCursor) as cur:
269
+ cur.execute("""
270
+ SELECT
271
+ c.id,
272
+ c.name,
273
+ c.numero_processo,
274
+ c.created_at,
275
+ COUNT(ci.id) as total_items,
276
+ COUNT(CASE WHEN cis.is_checked THEN 1 END) as completed_items,
277
+ CASE
278
+ WHEN COUNT(ci.id) > 0 THEN
279
+ ROUND((COUNT(CASE WHEN cis.is_checked THEN 1 END)::decimal / COUNT(ci.id)) * 100, 1)
280
+ ELSE 0
281
+ END as progress_percentage
282
+ FROM checklists c
283
+ LEFT JOIN checklist_items ci ON c.id = ci.checklist_id
284
+ LEFT JOIN current_item_states cis ON ci.id = cis.item_id
285
+ GROUP BY c.id, c.name, c.numero_processo, c.created_at
286
+ ORDER BY c.created_at DESC
287
+ """)
288
+ return cur.fetchall()
289
+
290
+ def get_general_stats():
291
+ """Retorna estatísticas gerais do sistema"""
292
+ with get_db_connection() as conn:
293
+ with conn.cursor(cursor_factory=RealDictCursor) as cur:
294
+ cur.execute("""
295
+ WITH checklist_progress AS (
296
+ SELECT
297
+ c.id,
298
+ c.numero_processo,
299
+ c.created_at,
300
+ COUNT(ci.id) as total_items,
301
+ COUNT(CASE WHEN cis.is_checked THEN 1 END) as completed_items,
302
+ CASE
303
+ WHEN COUNT(ci.id) > 0 THEN
304
+ ROUND((COUNT(CASE WHEN cis.is_checked THEN 1 END)::decimal / COUNT(ci.id)) * 100, 1)
305
+ ELSE 0
306
+ END as progress_percentage
307
+ FROM checklists c
308
+ LEFT JOIN checklist_items ci ON c.id = ci.checklist_id
309
+ LEFT JOIN current_item_states cis ON ci.id = cis.item_id
310
+ GROUP BY c.id, c.numero_processo, c.created_at
311
+ )
312
+ SELECT
313
+ COUNT(*) as total_checklists,
314
+ COUNT(DISTINCT numero_processo) as total_processos,
315
+ SUM(total_items) as total_items,
316
+ SUM(completed_items) as completed_items,
317
+ COUNT(CASE WHEN progress_percentage = 100 THEN 1 END) as completed_checklists,
318
+ MIN(created_at) as first_checklist_date,
319
+ MAX(created_at) as last_checklist_date,
320
+ ROUND(AVG(progress_percentage), 1) as avg_progress
321
+ FROM checklist_progress
322
+ """)
323
+ return cur.fetchone()
324
+
325
+ def get_checklist_interactions_summary():
326
+ """Retorna resumo de interações por checklist"""
327
+ with get_db_connection() as conn:
328
+ with conn.cursor(cursor_factory=RealDictCursor) as cur:
329
+ cur.execute("""
330
+ SELECT
331
+ c.id,
332
+ c.name,
333
+ c.numero_processo,
334
+ c.created_at,
335
+ COUNT(ii.id) as total_interactions,
336
+ COUNT(DISTINCT ii.item_id) as items_with_interactions,
337
+ MIN(ii.timestamp) as first_interaction,
338
+ MAX(ii.timestamp) as last_interaction
339
+ FROM checklists c
340
+ LEFT JOIN item_interactions ii ON c.id = ii.checklist_id
341
+ GROUP BY c.id, c.name, c.numero_processo, c.created_at
342
+ HAVING COUNT(ii.id) > 0
343
+ ORDER BY c.created_at DESC
344
+ """)
345
+ return cur.fetchall()
346
+
347
+ def get_process_summary():
348
+ """Retorna resumo por número de processo"""
349
+ with get_db_connection() as conn:
350
+ with conn.cursor(cursor_factory=RealDictCursor) as cur:
351
+ cur.execute("""
352
+ WITH checklist_progress AS (
353
+ SELECT
354
+ c.id,
355
+ c.numero_processo,
356
+ COUNT(ci.id) as total_items,
357
+ COUNT(CASE WHEN cis.is_checked THEN 1 END) as completed_items,
358
+ CASE
359
+ WHEN COUNT(ci.id) > 0 THEN
360
+ ROUND((COUNT(CASE WHEN cis.is_checked THEN 1 END)::decimal / COUNT(ci.id)) * 100, 1)
361
+ ELSE 0
362
+ END as progress_percentage
363
+ FROM checklists c
364
+ LEFT JOIN checklist_items ci ON c.id = ci.checklist_id
365
+ LEFT JOIN current_item_states cis ON ci.id = cis.item_id
366
+ WHERE c.numero_processo IS NOT NULL
367
+ GROUP BY c.id, c.numero_processo
368
+ )
369
+ SELECT
370
+ numero_processo,
371
+ COUNT(*) as total_checklists,
372
+ SUM(total_items) as total_items,
373
+ SUM(completed_items) as completed_items,
374
+ ROUND(AVG(progress_percentage), 1) as avg_progress
375
+ FROM checklist_progress
376
+ GROUP BY numero_processo
377
+ ORDER BY avg_progress DESC
378
+ """)
379
+ return cur.fetchall()
380
+
381
+ def get_fastest_processes():
382
+ """Retorna os processos mais rápidos baseado no tempo médio de conclusão"""
383
+ with get_db_connection() as conn:
384
+ with conn.cursor(cursor_factory=RealDictCursor) as cur:
385
+ cur.execute("""
386
+ WITH checklist_completion_times AS (
387
+ SELECT
388
+ c.id,
389
+ c.numero_processo,
390
+ c.name,
391
+ c.created_at,
392
+ MIN(ii.timestamp) as first_interaction,
393
+ MAX(ii.timestamp) as last_interaction,
394
+ COUNT(DISTINCT ci.id) as total_items,
395
+ COUNT(DISTINCT CASE WHEN cis.is_checked THEN ci.id END) as completed_items,
396
+ CASE
397
+ WHEN COUNT(ci.id) > 0 THEN
398
+ ROUND((COUNT(CASE WHEN cis.is_checked THEN 1 END)::decimal / COUNT(ci.id)) * 100, 1)
399
+ ELSE 0
400
+ END as progress_percentage
401
+ FROM checklists c
402
+ LEFT JOIN checklist_items ci ON c.id = ci.checklist_id
403
+ LEFT JOIN current_item_states cis ON ci.id = cis.item_id
404
+ LEFT JOIN item_interactions ii ON c.id = ii.checklist_id
405
+ WHERE c.numero_processo IS NOT NULL
406
+ GROUP BY c.id, c.numero_processo, c.name, c.created_at
407
+ ),
408
+ process_times AS (
409
+ SELECT
410
+ numero_processo,
411
+ COUNT(*) as total_checklists,
412
+ COUNT(CASE WHEN progress_percentage = 100 THEN 1 END) as completed_checklists,
413
+ AVG(progress_percentage) as avg_progress,
414
+ AVG(
415
+ CASE
416
+ WHEN first_interaction IS NOT NULL AND last_interaction IS NOT NULL
417
+ THEN EXTRACT(EPOCH FROM (last_interaction - first_interaction)) / 3600.0 -- horas
418
+ ELSE NULL
419
+ END
420
+ ) as avg_completion_hours,
421
+ AVG(
422
+ CASE
423
+ WHEN first_interaction IS NOT NULL
424
+ THEN EXTRACT(EPOCH FROM (first_interaction - created_at)) / 3600.0 -- horas até primeira interação
425
+ ELSE NULL
426
+ END
427
+ ) as avg_start_hours
428
+ FROM checklist_completion_times
429
+ GROUP BY numero_processo
430
+ )
431
+ SELECT
432
+ numero_processo,
433
+ total_checklists,
434
+ completed_checklists,
435
+ ROUND(avg_progress::numeric, 1) as avg_progress,
436
+ ROUND(avg_completion_hours::numeric, 2) as avg_completion_hours,
437
+ ROUND(avg_start_hours::numeric, 2) as avg_start_hours,
438
+ ROUND((avg_completion_hours + avg_start_hours)::numeric, 2) as total_avg_time_hours
439
+ FROM process_times
440
+ WHERE avg_completion_hours IS NOT NULL
441
+ OR avg_start_hours IS NOT NULL
442
+ ORDER BY total_avg_time_hours ASC NULLS LAST
443
+ LIMIT 10
444
+ """)
445
+ return cur.fetchall()
446
+
447
+ def get_process_deadline_analysis():
448
+ """Analisa prazos dos processos com deadline de 6 meses"""
449
+ with get_db_connection() as conn:
450
+ with conn.cursor(cursor_factory=RealDictCursor) as cur:
451
+ cur.execute("""
452
+ WITH process_progress AS (
453
+ SELECT
454
+ c.numero_processo,
455
+ c.created_at,
456
+ COUNT(ci.id) as total_items,
457
+ COUNT(CASE WHEN cis.is_checked THEN 1 END) as completed_items,
458
+ CASE
459
+ WHEN COUNT(ci.id) > 0 THEN
460
+ ROUND((COUNT(CASE WHEN cis.is_checked THEN 1 END)::decimal / COUNT(ci.id)) * 100, 1)
461
+ ELSE 0
462
+ END as progress_percentage,
463
+ MIN(ii.timestamp) as first_interaction,
464
+ MAX(ii.timestamp) as last_interaction,
465
+ COUNT(DISTINCT c.id) as total_checklists
466
+ FROM checklists c
467
+ LEFT JOIN checklist_items ci ON c.id = ci.checklist_id
468
+ LEFT JOIN current_item_states cis ON ci.id = cis.item_id
469
+ LEFT JOIN item_interactions ii ON c.id = ii.checklist_id
470
+ WHERE c.numero_processo IS NOT NULL
471
+ GROUP BY c.numero_processo, c.created_at
472
+ ),
473
+ process_summary AS (
474
+ SELECT
475
+ numero_processo,
476
+ MIN(created_at) as process_start_date,
477
+ MAX(created_at) as latest_checklist_date,
478
+ SUM(total_items) as total_items,
479
+ SUM(completed_items) as completed_items,
480
+ ROUND(AVG(progress_percentage), 1) as avg_progress,
481
+ MIN(first_interaction) as first_interaction,
482
+ MAX(last_interaction) as last_interaction,
483
+ SUM(total_checklists) as total_checklists
484
+ FROM process_progress
485
+ GROUP BY numero_processo
486
+ )
487
+ SELECT
488
+ numero_processo,
489
+ process_start_date,
490
+ latest_checklist_date,
491
+ total_items,
492
+ completed_items,
493
+ avg_progress,
494
+ first_interaction,
495
+ last_interaction,
496
+ total_checklists,
497
+ -- Cálculos de prazo (6 meses = 180 dias)
498
+ (process_start_date + INTERVAL '180 days')::date as deadline_date,
499
+ CURRENT_DATE - process_start_date::date as days_elapsed,
500
+ (process_start_date + INTERVAL '180 days')::date - CURRENT_DATE as days_remaining,
501
+ -- Velocidade e projeção
502
+ CASE
503
+ WHEN (CURRENT_DATE - process_start_date::date) > 0 AND avg_progress > 0 THEN
504
+ ROUND((avg_progress / (CURRENT_DATE - process_start_date::date)) *
505
+ (100 - avg_progress), 0)
506
+ ELSE NULL
507
+ END as projected_days_to_complete,
508
+ -- Status do prazo
509
+ CASE
510
+ WHEN avg_progress = 100 THEN 'CONCLUIDO'
511
+ WHEN (CURRENT_DATE - process_start_date::date) > 180 THEN 'ATRASADO'
512
+ WHEN (CURRENT_DATE - process_start_date::date) > 0 AND avg_progress > 0 THEN
513
+ CASE
514
+ WHEN ((avg_progress / (CURRENT_DATE - process_start_date::date)) *
515
+ (100 - avg_progress) + (CURRENT_DATE - process_start_date::date)) > 180
516
+ THEN 'RISCO_ATRASO'
517
+ WHEN ((avg_progress / (CURRENT_DATE - process_start_date::date)) *
518
+ (100 - avg_progress) + (CURRENT_DATE - process_start_date::date)) > 150
519
+ THEN 'EM_RISCO'
520
+ ELSE 'NO_PRAZO'
521
+ END
522
+ ELSE 'SEM_DADOS'
523
+ END as status_prazo
524
+ FROM process_summary
525
+ ORDER BY days_remaining ASC NULLS LAST
526
+ """)
527
+ return cur.fetchall()
528
+
529
+ def get_comprehensive_analysis_data():
530
+ """Retorna dados completos para análise com IA"""
531
+ with get_db_connection() as conn:
532
+ with conn.cursor(cursor_factory=RealDictCursor) as cur:
533
+ # Dados gerais dos processos
534
+ cur.execute("""
535
+ WITH process_stats AS (
536
+ SELECT
537
+ c.numero_processo,
538
+ COUNT(DISTINCT c.id) as total_checklists,
539
+ MIN(c.created_at) as process_start_date,
540
+ MAX(c.created_at) as latest_checklist_date,
541
+ COUNT(ci.id) as total_items,
542
+ COUNT(CASE WHEN cis.is_checked THEN 1 END) as completed_items,
543
+ ROUND(AVG(
544
+ CASE
545
+ WHEN COUNT(ci.id) OVER (PARTITION BY c.id) > 0 THEN
546
+ (COUNT(CASE WHEN cis.is_checked THEN 1 END) OVER (PARTITION BY c.id)::decimal /
547
+ COUNT(ci.id) OVER (PARTITION BY c.id)) * 100
548
+ ELSE 0
549
+ END
550
+ ), 1) as avg_progress_percentage,
551
+ COUNT(ii.id) as total_interactions,
552
+ MIN(ii.timestamp) as first_interaction,
553
+ MAX(ii.timestamp) as last_interaction,
554
+ CURRENT_DATE - MIN(c.created_at)::date as days_elapsed,
555
+ (MIN(c.created_at) + INTERVAL '180 days')::date - CURRENT_DATE as days_remaining_to_deadline
556
+ FROM checklists c
557
+ LEFT JOIN checklist_items ci ON c.id = ci.checklist_id
558
+ LEFT JOIN current_item_states cis ON ci.id = cis.item_id
559
+ LEFT JOIN item_interactions ii ON c.id = ii.checklist_id
560
+ WHERE c.numero_processo IS NOT NULL
561
+ GROUP BY c.numero_processo
562
+ )
563
+ SELECT
564
+ numero_processo,
565
+ total_checklists,
566
+ process_start_date,
567
+ latest_checklist_date,
568
+ total_items,
569
+ completed_items,
570
+ avg_progress_percentage,
571
+ total_interactions,
572
+ first_interaction,
573
+ last_interaction,
574
+ days_elapsed,
575
+ days_remaining_to_deadline,
576
+ CASE
577
+ WHEN avg_progress_percentage = 100 THEN 'CONCLUIDO'
578
+ WHEN days_remaining_to_deadline < 0 THEN 'ATRASADO'
579
+ WHEN days_remaining_to_deadline < 30 THEN 'EM_RISCO'
580
+ ELSE 'NO_PRAZO'
581
+ END as status_prazo,
582
+ CASE
583
+ WHEN days_elapsed > 0 AND avg_progress_percentage > 0 THEN
584
+ ROUND((avg_progress_percentage / days_elapsed) * (100 - avg_progress_percentage), 0)
585
+ ELSE NULL
586
+ END as projected_days_to_complete
587
+ FROM process_stats
588
+ ORDER BY days_remaining_to_deadline ASC
589
+ """)
590
+
591
+ process_data = cur.fetchall()
592
+
593
+ # Estatísticas globais
594
+ cur.execute("""
595
+ SELECT
596
+ COUNT(DISTINCT c.id) as total_checklists_global,
597
+ COUNT(DISTINCT c.numero_processo) as total_processes,
598
+ COUNT(ci.id) as total_items_global,
599
+ COUNT(CASE WHEN cis.is_checked THEN 1 END) as completed_items_global,
600
+ COUNT(ii.id) as total_interactions_global,
601
+ MIN(c.created_at) as earliest_process,
602
+ MAX(c.created_at) as latest_process
603
+ FROM checklists c
604
+ LEFT JOIN checklist_items ci ON c.id = ci.checklist_id
605
+ LEFT JOIN current_item_states cis ON ci.id = cis.item_id
606
+ LEFT JOIN item_interactions ii ON c.id = ii.checklist_id
607
+ """)
608
+
609
+ global_stats = cur.fetchone()
610
+
611
+ return {
612
+ 'process_data': process_data,
613
+ 'global_stats': global_stats
614
+ }
615
+
616
+ # Função para testar conexão
617
+ def test_connection():
618
+ """Testa a conexão com o banco de dados"""
619
+ try:
620
+ with get_db_connection() as conn:
621
+ with conn.cursor() as cur:
622
+ cur.execute("SELECT version()")
623
+ version = cur.fetchone()
624
+ print(f"Conectado ao PostgreSQL: {version[0]}")
625
+ return True
626
+ except Exception as e:
627
+ print(f"Erro ao conectar: {e}")
628
+ return False