roundb commited on
Commit
bc3a3f4
Β·
verified Β·
1 Parent(s): c8876ce

Upload 5 files

Browse files
Dockerfile ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ # Utilizador nΓ£o-root exigido pelo Hugging Face Spaces
4
+ RUN useradd -m -u 1000 appuser
5
+
6
+ WORKDIR /app
7
+
8
+ # Instalar dependΓͺncias do sistema
9
+ RUN apt-get update && apt-get install -y --no-install-recommends \
10
+ build-essential \
11
+ && rm -rf /var/lib/apt/lists/*
12
+
13
+ # Copiar e instalar dependΓͺncias Python
14
+ COPY requirements.txt .
15
+ RUN pip install --no-cache-dir -r requirements.txt
16
+
17
+ # Copiar todos os ficheiros da app
18
+ COPY --chown=appuser:appuser . .
19
+
20
+ # Mudar para utilizador nΓ£o-root
21
+ USER appuser
22
+
23
+ # Porta obrigatΓ³ria no Hugging Face Spaces
24
+ EXPOSE 7860
25
+
26
+ # Arrancar com gunicorn (mais robusto que o servidor de desenvolvimento do Dash)
27
+ CMD ["gunicorn", "--bind", "0.0.0.0:7860", "--workers", "1", "--timeout", "120", "app:server"]
app.py ADDED
@@ -0,0 +1,884 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import numpy as np
3
+ import plotly.graph_objects as go
4
+ import plotly.express as px
5
+ from dash import Dash, dcc, html, Input, Output, callback
6
+ import dash_bootstrap_components as dbc
7
+ import warnings
8
+ warnings.filterwarnings('ignore')
9
+
10
+ # ─────────────────────────────────────────────────────────────────────────────
11
+ # 1. CARREGAMENTO E PREPARAÇÃO DOS DADOS
12
+ # ─────────────────────────────────────────────────────────────────────────────
13
+ df_raw = pd.read_csv(
14
+ 'tarefasss_datas_corrigidas_final.csv',
15
+ sep=';', encoding='latin-1'
16
+ )
17
+ df_raw = df_raw[df_raw.iloc[:, 0].notna()].copy()
18
+ df_raw.columns = [
19
+ 'SUB_CIP','PROJETO','DESIGNACAO','TIPO','RB_STATUS','COLABORADOR','OBSERVACOES',
20
+ 'DATA_ADJ_CLIENTE','DATA_LIMITE_SLA','TEMPO_EXECUCAO_DIAS','PCT_SLA',
21
+ 'DATA_ENTREGA_V0','DATA_1_REENTREGA_V1','DATA_2_REENTREGA_V2','DATA_3_REENTREGA_V3',
22
+ 'DATA_VALIDACAO','QTD_VERSOES','TAXA_REJEICAO_VERSOES','TAXA_ACEITACAO_VERSOES',
23
+ 'QTD_REVISOES','INDICE_QUALIDADE','COLABORADOR_PROD_V0','DATA_INICIO_PROD_V0',
24
+ 'DATA_FIM_PROD_V0','COLABORADOR_QC_V0','DATA_INICIO_QC_V0','DATA_FIM_QC_V0',
25
+ 'PCT_ERROS_VISUAIS_V0','PCT_ERROS_MACRO_V0','TAXA_APROVACAO_INTERNA_V0',
26
+ 'COLABORADOR_PROD_V1','DATA_INICIO_PROD_V1','DATA_FIM_PROD_V1','COLABORADOR_QC_V1',
27
+ 'DATA_INICIO_QC_V1','DATA_FIM_QC_V1','PCT_ERROS_VISUAIS_V1','PCT_ERROS_MACRO_V1',
28
+ 'TAXA_ERRO_GLOBAL_V1','COLABORADOR_PROD_V2','DATA_INICIO_PROD_V2','DATA_FIM_PROD_V2',
29
+ 'COLABORADOR_QC_V2','DATA_INICIO_QC_V2','DATA_FIM_QC_V2','PCT_ERROS_VISUAIS_V2',
30
+ 'PCT_ERROS_MACRO_V2','TAXA_ERRO_GLOBAL_V2','RESPOSTA_CLIENTE','DATA_RESPOSTA'
31
+ ]
32
+
33
+ # ConversΓ΅es numΓ©ricas
34
+ for col in ['TEMPO_EXECUCAO_DIAS','QTD_VERSOES','QTD_REVISOES']:
35
+ df_raw[col] = pd.to_numeric(
36
+ df_raw[col].astype(str).str.replace(',', '.').str.strip(),
37
+ errors='coerce'
38
+ )
39
+ for col in ['PCT_SLA','INDICE_QUALIDADE','TAXA_REJEICAO_VERSOES','TAXA_ACEITACAO_VERSOES',
40
+ 'PCT_ERROS_VISUAIS_V0','PCT_ERROS_MACRO_V0','TAXA_APROVACAO_INTERNA_V0']:
41
+ df_raw[col] = pd.to_numeric(
42
+ df_raw[col].astype(str).str.replace('%','').str.replace(',','.').str.strip(),
43
+ errors='coerce'
44
+ )
45
+
46
+ # Datas
47
+ for col in ['DATA_ADJ_CLIENTE','DATA_LIMITE_SLA','DATA_ENTREGA_V0','DATA_VALIDACAO']:
48
+ df_raw[col] = pd.to_datetime(df_raw[col], dayfirst=True, errors='coerce')
49
+
50
+ df_raw['MES_ADJ'] = df_raw['DATA_ADJ_CLIENTE'].dt.to_period('M').astype(str)
51
+ df_raw['MES_LABEL'] = df_raw['DATA_ADJ_CLIENTE'].dt.strftime('%b %Y')
52
+
53
+ # Normalizar colaborador
54
+ df_raw['COLABORADOR'] = df_raw['COLABORADOR'].str.strip().str.upper()
55
+ df_raw['COLABORADOR'] = df_raw['COLABORADOR'].replace({'STEPHANIE FARIA': 'STEPHANIE FARIA'})
56
+
57
+ # ClassificaΓ§Γ£o SLA
58
+ df_raw['SLA_OK'] = df_raw['PCT_SLA'].apply(
59
+ lambda x: 'Dentro do SLA' if pd.notna(x) and x <= 100 else ('Fora do SLA' if pd.notna(x) else 'N/D')
60
+ )
61
+
62
+ # Status simplificado para agrupamento
63
+ status_map = {
64
+ '08 PROJETO VALIDADO': 'Validado',
65
+ '13 FATURADO': 'Faturado',
66
+ '07 VALIDAÇO ORANGE': 'Validação Orange',
67
+ '12 CANCELADO': 'Cancelado',
68
+ '09 TRABALHOS EM CURSO': 'Em Curso',
69
+ '11.1 AGUARDA RT': 'Aguarda RT',
70
+ '03 POR INICIAR CQ': 'Por Iniciar CQ',
71
+ '02.1 PROJETO PENDENTE CLIENTE': 'Pendente Cliente',
72
+ '03.3 CQ SOGETREL': 'CQ Sogetrel',
73
+ '06.4 AGUARDA PMV+DT': 'Aguarda PMV+DT',
74
+ '06.2 AGUARDA DEVIS': 'Aguarda Devis',
75
+ '02.3 PROJETO EM CURSO': 'Projeto em Curso',
76
+ }
77
+ df_raw['STATUS_LABEL'] = df_raw['RB_STATUS'].map(status_map).fillna(df_raw['RB_STATUS'])
78
+
79
+ # ─────────────────────────────────────────────────────────────────────────────
80
+ # 2. PALETA E TEMA
81
+ # ─────────────────────────────────────────────────────────────────────────────
82
+ COLORS = {
83
+ 'bg': '#0D1117',
84
+ 'card': '#161B22',
85
+ 'card2': '#1C2128',
86
+ 'border': '#30363D',
87
+ 'primary': '#2563EB',
88
+ 'accent': '#3B82F6',
89
+ 'success': '#10B981',
90
+ 'warning': '#F59E0B',
91
+ 'danger': '#EF4444',
92
+ 'text': '#E6EDF3',
93
+ 'muted': '#8B949E',
94
+ 'gold': '#F59E0B',
95
+ 'purple': '#8B5CF6',
96
+ 'teal': '#14B8A6',
97
+ }
98
+
99
+ STATUS_COLORS = {
100
+ 'Validado': '#10B981',
101
+ 'Faturado': '#2563EB',
102
+ 'ValidaΓ§Γ£o Orange': '#F59E0B',
103
+ 'Cancelado': '#EF4444',
104
+ 'Em Curso': '#8B5CF6',
105
+ 'Aguarda RT': '#14B8A6',
106
+ 'Por Iniciar CQ': '#6366F1',
107
+ 'Pendente Cliente': '#F97316',
108
+ 'CQ Sogetrel': '#EC4899',
109
+ 'Aguarda PMV+DT': '#84CC16',
110
+ 'Aguarda Devis': '#06B6D4',
111
+ 'Projeto em Curso': '#A78BFA',
112
+ }
113
+
114
+ PLOTLY_LAYOUT = dict(
115
+ paper_bgcolor='rgba(0,0,0,0)',
116
+ plot_bgcolor='rgba(0,0,0,0)',
117
+ font=dict(family='Inter, Segoe UI, sans-serif', color=COLORS['text'], size=12),
118
+ margin=dict(l=10, r=10, t=30, b=10),
119
+ legend=dict(
120
+ bgcolor='rgba(22,27,34,0.8)',
121
+ bordercolor=COLORS['border'],
122
+ borderwidth=1,
123
+ font=dict(size=11),
124
+ ),
125
+ xaxis=dict(
126
+ gridcolor=COLORS['border'], gridwidth=0.5,
127
+ linecolor=COLORS['border'], tickfont=dict(size=11),
128
+ ),
129
+ yaxis=dict(
130
+ gridcolor=COLORS['border'], gridwidth=0.5,
131
+ linecolor=COLORS['border'], tickfont=dict(size=11),
132
+ ),
133
+ )
134
+
135
+ # ─────────────────────────────────────────────────────────────────────────────
136
+ # 3. HELPERS
137
+ # ─────────────────────────────────────────────────────────────────────────────
138
+ def kpi_card(title, value, subtitle=None, color=COLORS['primary'], icon='πŸ“Š'):
139
+ return dbc.Col(
140
+ html.Div([
141
+ html.Div([
142
+ html.Span(icon, style={'fontSize': '22px', 'marginRight': '8px'}),
143
+ html.Span(title, style={
144
+ 'fontSize': '11px', 'fontWeight': '600',
145
+ 'color': COLORS['muted'], 'textTransform': 'uppercase',
146
+ 'letterSpacing': '0.08em'
147
+ }),
148
+ ], style={'display': 'flex', 'alignItems': 'center', 'marginBottom': '8px'}),
149
+ html.Div(value, style={
150
+ 'fontSize': '32px', 'fontWeight': '700',
151
+ 'color': color, 'lineHeight': '1',
152
+ }),
153
+ html.Div(subtitle or '', style={
154
+ 'fontSize': '11px', 'color': COLORS['muted'], 'marginTop': '6px'
155
+ }),
156
+ ], style={
157
+ 'background': COLORS['card'],
158
+ 'border': f'1px solid {COLORS["border"]}',
159
+ 'borderTop': f'3px solid {color}',
160
+ 'borderRadius': '8px',
161
+ 'padding': '18px 20px',
162
+ 'height': '100%',
163
+ }),
164
+ xs=12, sm=6, md=3,
165
+ )
166
+
167
+ def section_title(text):
168
+ return html.Div(text, style={
169
+ 'fontSize': '13px', 'fontWeight': '700',
170
+ 'color': COLORS['muted'], 'textTransform': 'uppercase',
171
+ 'letterSpacing': '0.1em', 'marginBottom': '12px',
172
+ 'paddingBottom': '8px', 'borderBottom': f'1px solid {COLORS["border"]}',
173
+ })
174
+
175
+ def chart_card(children, title=None, height=None):
176
+ style = {
177
+ 'background': COLORS['card'],
178
+ 'border': f'1px solid {COLORS["border"]}',
179
+ 'borderRadius': '8px',
180
+ 'padding': '16px',
181
+ 'marginBottom': '16px',
182
+ }
183
+ if height:
184
+ style['height'] = height
185
+ content = []
186
+ if title:
187
+ content.append(html.Div(title, style={
188
+ 'fontSize': '13px', 'fontWeight': '600',
189
+ 'color': COLORS['text'], 'marginBottom': '12px',
190
+ }))
191
+ content.append(children)
192
+ return html.Div(content, style=style)
193
+
194
+ # ─────────────────────────────────────────────────────────────────────────────
195
+ # 4. APP
196
+ # ─────────────────────────────────────────────────────────────────────────────
197
+ app = Dash(
198
+ __name__,
199
+ external_stylesheets=[
200
+ dbc.themes.BOOTSTRAP,
201
+ 'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap',
202
+ ],
203
+ title='Dashboard Executivo β€” GestΓ£o de Projetos',
204
+ suppress_callback_exceptions=True,
205
+ )
206
+
207
+ # ── Filtros disponΓ­veis ───────────────────────────────────────────────────────
208
+ meses_sorted = sorted(df_raw['MES_ADJ'].dropna().unique())
209
+ colaboradores = sorted(df_raw['COLABORADOR'].dropna().unique())
210
+ tipos = sorted(df_raw['TIPO'].dropna().unique())
211
+
212
+ # ─────────────────────────────────────────────────────────────────────────────
213
+ # 5. LAYOUT
214
+ # ─────────────────────────────────────────────────────────────────────────────
215
+ app.layout = html.Div([
216
+
217
+ # ── HEADER ────────────────────────────────────────────────────────────────
218
+ html.Div([
219
+ dbc.Container([
220
+ dbc.Row([
221
+ dbc.Col([
222
+ html.Div([
223
+ html.Span('⬑', style={'color': COLORS['primary'], 'fontSize': '24px', 'marginRight': '10px'}),
224
+ html.Span('GESTÃO DE PROJETOS', style={
225
+ 'fontSize': '18px', 'fontWeight': '700',
226
+ 'color': COLORS['text'], 'letterSpacing': '0.05em',
227
+ }),
228
+ html.Span(' Β· Dashboard Executivo', style={
229
+ 'fontSize': '14px', 'color': COLORS['muted'],
230
+ 'marginLeft': '8px',
231
+ }),
232
+ ], style={'display': 'flex', 'alignItems': 'center'}),
233
+ ], md=8),
234
+ dbc.Col([
235
+ html.Div('Set 2025 – Fev 2026', style={
236
+ 'textAlign': 'right', 'color': COLORS['muted'],
237
+ 'fontSize': '12px', 'paddingTop': '4px',
238
+ }),
239
+ ], md=4),
240
+ ], align='center'),
241
+ ], fluid=True),
242
+ ], style={
243
+ 'background': COLORS['card'],
244
+ 'borderBottom': f'1px solid {COLORS["border"]}',
245
+ 'padding': '14px 0',
246
+ 'position': 'sticky', 'top': '0', 'zIndex': '1000',
247
+ }),
248
+
249
+ # ── FILTROS ───────────────────────────────────────────────────────────────
250
+ dbc.Container([
251
+ html.Div([
252
+ dbc.Row([
253
+ dbc.Col([
254
+ html.Label('PerΓ­odo', style={'fontSize': '11px', 'color': COLORS['muted'], 'fontWeight': '600'}),
255
+ dcc.Dropdown(
256
+ id='filter-mes',
257
+ options=[{'label': m, 'value': m} for m in meses_sorted],
258
+ multi=True, placeholder='Todos os meses',
259
+ style={'fontSize': '12px'},
260
+ className='dark-dropdown',
261
+ ),
262
+ ], md=3),
263
+ dbc.Col([
264
+ html.Label('Colaborador', style={'fontSize': '11px', 'color': COLORS['muted'], 'fontWeight': '600'}),
265
+ dcc.Dropdown(
266
+ id='filter-colab',
267
+ options=[{'label': c, 'value': c} for c in colaboradores],
268
+ multi=True, placeholder='Todos',
269
+ style={'fontSize': '12px'},
270
+ ),
271
+ ], md=3),
272
+ dbc.Col([
273
+ html.Label('Tipo de Projeto', style={'fontSize': '11px', 'color': COLORS['muted'], 'fontWeight': '600'}),
274
+ dcc.Dropdown(
275
+ id='filter-tipo',
276
+ options=[{'label': t, 'value': t} for t in tipos],
277
+ multi=True, placeholder='Todos',
278
+ style={'fontSize': '12px'},
279
+ ),
280
+ ], md=3),
281
+ dbc.Col([
282
+ html.Label('Status', style={'fontSize': '11px', 'color': COLORS['muted'], 'fontWeight': '600'}),
283
+ dcc.Dropdown(
284
+ id='filter-status',
285
+ options=[{'label': s, 'value': s} for s in sorted(df_raw['STATUS_LABEL'].unique())],
286
+ multi=True, placeholder='Todos',
287
+ style={'fontSize': '12px'},
288
+ ),
289
+ ], md=3),
290
+ ]),
291
+ ], style={
292
+ 'background': COLORS['card2'],
293
+ 'border': f'1px solid {COLORS["border"]}',
294
+ 'borderRadius': '8px',
295
+ 'padding': '14px 16px',
296
+ 'marginTop': '16px',
297
+ 'marginBottom': '16px',
298
+ }),
299
+
300
+ # ── KPIs ──────────────────────────────────────────────────────────────
301
+ html.Div(id='kpi-row', style={'marginBottom': '16px'}),
302
+
303
+ # ── LINHA 1: EvoluΓ§Γ£o mensal + DistribuiΓ§Γ£o por Status ────────────────
304
+ dbc.Row([
305
+ dbc.Col(
306
+ chart_card(
307
+ dcc.Graph(id='chart-evolucao', config={'displayModeBar': False},
308
+ style={'height': '300px'}),
309
+ title='EvoluΓ§Γ£o Mensal de Projetos Adjudicados'
310
+ ),
311
+ md=7
312
+ ),
313
+ dbc.Col(
314
+ chart_card(
315
+ dcc.Graph(id='chart-status', config={'displayModeBar': False},
316
+ style={'height': '300px'}),
317
+ title='DistribuiΓ§Γ£o por Status'
318
+ ),
319
+ md=5
320
+ ),
321
+ ]),
322
+
323
+ # ── LINHA 2: DistribuiΓ§Γ£o por Tipo + Resposta Cliente ─────────────────
324
+ dbc.Row([
325
+ dbc.Col(
326
+ chart_card(
327
+ dcc.Graph(id='chart-tipo', config={'displayModeBar': False},
328
+ style={'height': '300px'}),
329
+ title='Projetos por Tipo'
330
+ ),
331
+ md=5
332
+ ),
333
+ dbc.Col(
334
+ chart_card(
335
+ dcc.Graph(id='chart-resposta', config={'displayModeBar': False},
336
+ style={'height': '300px'}),
337
+ title='Resposta do Cliente (OK vs NOK)'
338
+ ),
339
+ md=3
340
+ ),
341
+ dbc.Col(
342
+ chart_card(
343
+ dcc.Graph(id='chart-sla', config={'displayModeBar': False},
344
+ style={'height': '300px'}),
345
+ title='Cumprimento de SLA'
346
+ ),
347
+ md=4
348
+ ),
349
+ ]),
350
+
351
+ # ── LINHA 3: Performance Colaboradores ────────────────────────────────
352
+ dbc.Row([
353
+ dbc.Col(
354
+ chart_card(
355
+ dcc.Graph(id='chart-colab-volume', config={'displayModeBar': False},
356
+ style={'height': '320px'}),
357
+ title='Volume de Projetos por Colaborador'
358
+ ),
359
+ md=6
360
+ ),
361
+ dbc.Col(
362
+ chart_card(
363
+ dcc.Graph(id='chart-colab-qualidade', config={'displayModeBar': False},
364
+ style={'height': '320px'}),
365
+ title='Índice de Qualidade Médio por Colaborador'
366
+ ),
367
+ md=6
368
+ ),
369
+ ]),
370
+
371
+ # ── LINHA 4: Tempo ExecuΓ§Γ£o + VersΓ΅es ─────────────────────────────────
372
+ dbc.Row([
373
+ dbc.Col(
374
+ chart_card(
375
+ dcc.Graph(id='chart-tempo', config={'displayModeBar': False},
376
+ style={'height': '300px'}),
377
+ title='DistribuiΓ§Γ£o do Tempo de ExecuΓ§Γ£o (dias)'
378
+ ),
379
+ md=6
380
+ ),
381
+ dbc.Col(
382
+ chart_card(
383
+ dcc.Graph(id='chart-versoes', config={'displayModeBar': False},
384
+ style={'height': '300px'}),
385
+ title='NΒΊ de VersΓ΅es por Projeto'
386
+ ),
387
+ md=3
388
+ ),
389
+ dbc.Col(
390
+ chart_card(
391
+ dcc.Graph(id='chart-revisoes', config={'displayModeBar': False},
392
+ style={'height': '300px'}),
393
+ title='NΒΊ de RevisΓ΅es por Projeto'
394
+ ),
395
+ md=3
396
+ ),
397
+ ]),
398
+
399
+ # ── LINHA 5: Erros QC ─────────────────────────────────────────────────
400
+ dbc.Row([
401
+ dbc.Col(
402
+ chart_card(
403
+ dcc.Graph(id='chart-erros', config={'displayModeBar': False},
404
+ style={'height': '300px'}),
405
+ title='Taxa de Erros QC β€” Visuais vs Macro (V0)'
406
+ ),
407
+ md=6
408
+ ),
409
+ dbc.Col(
410
+ chart_card(
411
+ dcc.Graph(id='chart-qualidade-hist', config={'displayModeBar': False},
412
+ style={'height': '300px'}),
413
+ title='Distribuição do Índice de Qualidade'
414
+ ),
415
+ md=6
416
+ ),
417
+ ]),
418
+
419
+ # ── RODAPΓ‰ ────────────────────────────────────────────────────────────
420
+ html.Div([
421
+ html.Span('Dashboard Executivo Β· GestΓ£o de Projetos', style={'color': COLORS['muted'], 'fontSize': '11px'}),
422
+ html.Span(' Β· Dados: Set 2025 – Fev 2026', style={'color': COLORS['border'], 'fontSize': '11px'}),
423
+ ], style={
424
+ 'textAlign': 'center', 'padding': '24px 0 16px',
425
+ 'borderTop': f'1px solid {COLORS["border"]}',
426
+ 'marginTop': '8px',
427
+ }),
428
+
429
+ ], fluid=True, style={'maxWidth': '1400px'}),
430
+
431
+ ], style={
432
+ 'background': COLORS['bg'],
433
+ 'minHeight': '100vh',
434
+ 'fontFamily': 'Inter, Segoe UI, sans-serif',
435
+ 'color': COLORS['text'],
436
+ })
437
+
438
+ # ─────────────────────────────────────────────────────────────────────────────
439
+ # 6. CALLBACKS
440
+ # ──────────���──────────────────────────────────────────────────────────────────
441
+ def filter_df(mes, colab, tipo, status):
442
+ df = df_raw.copy()
443
+ if mes:
444
+ df = df[df['MES_ADJ'].isin(mes)]
445
+ if colab:
446
+ df = df[df['COLABORADOR'].isin(colab)]
447
+ if tipo:
448
+ df = df[df['TIPO'].isin(tipo)]
449
+ if status:
450
+ df = df[df['STATUS_LABEL'].isin(status)]
451
+ return df
452
+
453
+
454
+ @callback(
455
+ Output('kpi-row', 'children'),
456
+ Input('filter-mes', 'value'),
457
+ Input('filter-colab', 'value'),
458
+ Input('filter-tipo', 'value'),
459
+ Input('filter-status', 'value'),
460
+ )
461
+ def update_kpis(mes, colab, tipo, status):
462
+ df = filter_df(mes, colab, tipo, status)
463
+ total = len(df)
464
+ validados = len(df[df['STATUS_LABEL'].isin(['Validado', 'Faturado', 'ValidaΓ§Γ£o Orange'])])
465
+ pct_valid = f"{validados/total*100:.0f}%" if total else "β€”"
466
+ sla_ok = len(df[df['SLA_OK'] == 'Dentro do SLA'])
467
+ pct_sla = f"{sla_ok/total*100:.0f}%" if total else "β€”"
468
+ iq_mean = df['INDICE_QUALIDADE'].mean()
469
+ iq_str = f"{iq_mean:.0f}%" if pd.notna(iq_mean) else "β€”"
470
+ tempo_med = df['TEMPO_EXECUCAO_DIAS'].median()
471
+ tempo_str = f"{tempo_med:.0f} dias" if pd.notna(tempo_med) else "β€”"
472
+ cancelados = len(df[df['STATUS_LABEL'] == 'Cancelado'])
473
+ resp_ok = len(df[df['RESPOSTA_CLIENTE'] == 'OK'])
474
+ resp_total = len(df[df['RESPOSTA_CLIENTE'].isin(['OK', 'NOK'])])
475
+ pct_resp = f"{resp_ok/resp_total*100:.0f}%" if resp_total else "β€”"
476
+
477
+ return dbc.Row([
478
+ kpi_card('Total de Projetos', f"{total:,}", 'no perΓ­odo selecionado', COLORS['primary'], 'πŸ“'),
479
+ kpi_card('Taxa de ConclusΓ£o', pct_valid, f'{validados} projetos concluΓ­dos', COLORS['success'], 'βœ…'),
480
+ kpi_card('Cumprimento SLA', pct_sla, f'{sla_ok} dentro do prazo', COLORS['teal'], '⏱'),
481
+ kpi_card('Índice de Qualidade', iq_str, 'média dos projetos avaliados', COLORS['gold'], '⭐'),
482
+ kpi_card('Tempo Mediano', tempo_str, 'tempo de execuΓ§Γ£o (dias)', COLORS['purple'], 'πŸ“…'),
483
+ kpi_card('AprovaΓ§Γ£o Cliente', pct_resp, f'{resp_ok} OK de {resp_total} avaliados', COLORS['accent'], 'πŸ‘€'),
484
+ kpi_card('Cancelamentos', f"{cancelados}", 'projetos cancelados', COLORS['danger'], '❌'),
485
+ kpi_card('Colaboradores Ativos', str(df['COLABORADOR'].nunique()), 'no perΓ­odo', COLORS['muted'], 'πŸ‘₯'),
486
+ ], className='g-2 mb-3')
487
+
488
+
489
+ @callback(
490
+ Output('chart-evolucao', 'figure'),
491
+ Input('filter-mes', 'value'),
492
+ Input('filter-colab', 'value'),
493
+ Input('filter-tipo', 'value'),
494
+ Input('filter-status', 'value'),
495
+ )
496
+ def chart_evolucao(mes, colab, tipo, status):
497
+ df = filter_df(mes, colab, tipo, status)
498
+ monthly = df.groupby('MES_ADJ').size().reset_index(name='count')
499
+ monthly = monthly.sort_values('MES_ADJ')
500
+
501
+ fig = go.Figure()
502
+ fig.add_trace(go.Bar(
503
+ x=monthly['MES_ADJ'], y=monthly['count'],
504
+ marker_color=COLORS['primary'],
505
+ marker_line_width=0,
506
+ opacity=0.85,
507
+ name='Projetos',
508
+ hovertemplate='<b>%{x}</b><br>%{y} projetos<extra></extra>',
509
+ ))
510
+ fig.add_trace(go.Scatter(
511
+ x=monthly['MES_ADJ'], y=monthly['count'],
512
+ mode='lines+markers',
513
+ line=dict(color=COLORS['accent'], width=2),
514
+ marker=dict(size=6, color=COLORS['accent']),
515
+ name='TendΓͺncia',
516
+ hovertemplate='<b>%{x}</b><br>%{y} projetos<extra></extra>',
517
+ ))
518
+ fig.update_layout(**PLOTLY_LAYOUT, showlegend=True)
519
+ return fig
520
+
521
+
522
+ @callback(
523
+ Output('chart-status', 'figure'),
524
+ Input('filter-mes', 'value'),
525
+ Input('filter-colab', 'value'),
526
+ Input('filter-tipo', 'value'),
527
+ Input('filter-status', 'value'),
528
+ )
529
+ def chart_status(mes, colab, tipo, status):
530
+ df = filter_df(mes, colab, tipo, status)
531
+ counts = df['STATUS_LABEL'].value_counts().reset_index()
532
+ counts.columns = ['status', 'count']
533
+ colors = [STATUS_COLORS.get(s, COLORS['muted']) for s in counts['status']]
534
+
535
+ fig = go.Figure(go.Pie(
536
+ labels=counts['status'],
537
+ values=counts['count'],
538
+ hole=0.55,
539
+ marker=dict(colors=colors, line=dict(color=COLORS['bg'], width=2)),
540
+ textinfo='percent',
541
+ hovertemplate='<b>%{label}</b><br>%{value} projetos (%{percent})<extra></extra>',
542
+ ))
543
+ layout = dict(**PLOTLY_LAYOUT)
544
+ layout['legend'] = dict(
545
+ orientation='v', x=1.02, y=0.5,
546
+ bgcolor='rgba(0,0,0,0)',
547
+ font=dict(size=10),
548
+ )
549
+ layout['annotations'] = [dict(
550
+ text=f"<b>{len(df)}</b><br><span style='font-size:10px'>total</span>",
551
+ x=0.5, y=0.5, font_size=16, showarrow=False,
552
+ font=dict(color=COLORS['text']),
553
+ )]
554
+ fig.update_layout(**layout)
555
+ return fig
556
+
557
+
558
+ @callback(
559
+ Output('chart-tipo', 'figure'),
560
+ Input('filter-mes', 'value'),
561
+ Input('filter-colab', 'value'),
562
+ Input('filter-tipo', 'value'),
563
+ Input('filter-status', 'value'),
564
+ )
565
+ def chart_tipo(mes, colab, tipo, status):
566
+ df = filter_df(mes, colab, tipo, status)
567
+ counts = df['TIPO'].value_counts().reset_index()
568
+ counts.columns = ['tipo', 'count']
569
+ counts = counts.sort_values('count', ascending=True)
570
+
571
+ fig = go.Figure(go.Bar(
572
+ x=counts['count'], y=counts['tipo'],
573
+ orientation='h',
574
+ marker=dict(
575
+ color=counts['count'],
576
+ colorscale=[[0, '#1e3a5f'], [1, COLORS['primary']]],
577
+ line=dict(width=0),
578
+ ),
579
+ hovertemplate='<b>%{y}</b><br>%{x} projetos<extra></extra>',
580
+ ))
581
+ fig.update_layout(**PLOTLY_LAYOUT, showlegend=False)
582
+ fig.update_xaxes(title_text='')
583
+ fig.update_yaxes(title_text='')
584
+ return fig
585
+
586
+
587
+ @callback(
588
+ Output('chart-resposta', 'figure'),
589
+ Input('filter-mes', 'value'),
590
+ Input('filter-colab', 'value'),
591
+ Input('filter-tipo', 'value'),
592
+ Input('filter-status', 'value'),
593
+ )
594
+ def chart_resposta(mes, colab, tipo, status):
595
+ df = filter_df(mes, colab, tipo, status)
596
+ df_r = df[df['RESPOSTA_CLIENTE'].isin(['OK', 'NOK'])]
597
+ counts = df_r['RESPOSTA_CLIENTE'].value_counts().reset_index()
598
+ counts.columns = ['resp', 'count']
599
+ color_map = {'OK': COLORS['success'], 'NOK': COLORS['danger']}
600
+ colors = [color_map.get(r, COLORS['muted']) for r in counts['resp']]
601
+
602
+ fig = go.Figure(go.Pie(
603
+ labels=counts['resp'],
604
+ values=counts['count'],
605
+ hole=0.6,
606
+ marker=dict(colors=colors, line=dict(color=COLORS['bg'], width=3)),
607
+ textinfo='percent+label',
608
+ hovertemplate='<b>%{label}</b><br>%{value} projetos<extra></extra>',
609
+ ))
610
+ fig.update_layout(**PLOTLY_LAYOUT, showlegend=False)
611
+ return fig
612
+
613
+
614
+ @callback(
615
+ Output('chart-sla', 'figure'),
616
+ Input('filter-mes', 'value'),
617
+ Input('filter-colab', 'value'),
618
+ Input('filter-tipo', 'value'),
619
+ Input('filter-status', 'value'),
620
+ )
621
+ def chart_sla(mes, colab, tipo, status):
622
+ df = filter_df(mes, colab, tipo, status)
623
+ df_s = df[df['SLA_OK'] != 'N/D']
624
+ counts = df_s['SLA_OK'].value_counts().reset_index()
625
+ counts.columns = ['sla', 'count']
626
+ color_map = {'Dentro do SLA': COLORS['success'], 'Fora do SLA': COLORS['danger']}
627
+ colors = [color_map.get(s, COLORS['muted']) for s in counts['sla']]
628
+
629
+ fig = go.Figure(go.Pie(
630
+ labels=counts['sla'],
631
+ values=counts['count'],
632
+ hole=0.6,
633
+ marker=dict(colors=colors, line=dict(color=COLORS['bg'], width=3)),
634
+ textinfo='percent+label',
635
+ hovertemplate='<b>%{label}</b><br>%{value} projetos<extra></extra>',
636
+ ))
637
+ fig.update_layout(**PLOTLY_LAYOUT, showlegend=False)
638
+ return fig
639
+
640
+
641
+ @callback(
642
+ Output('chart-colab-volume', 'figure'),
643
+ Input('filter-mes', 'value'),
644
+ Input('filter-colab', 'value'),
645
+ Input('filter-tipo', 'value'),
646
+ Input('filter-status', 'value'),
647
+ )
648
+ def chart_colab_volume(mes, colab, tipo, status):
649
+ df = filter_df(mes, colab, tipo, status)
650
+ df_c = df[df['COLABORADOR'].notna()]
651
+ counts = df_c.groupby(['COLABORADOR', 'STATUS_LABEL']).size().reset_index(name='count')
652
+ top_colabs = df_c['COLABORADOR'].value_counts().head(8).index.tolist()
653
+ counts = counts[counts['COLABORADOR'].isin(top_colabs)]
654
+
655
+ fig = go.Figure()
656
+ for s, color in STATUS_COLORS.items():
657
+ sub = counts[counts['STATUS_LABEL'] == s]
658
+ if not sub.empty:
659
+ fig.add_trace(go.Bar(
660
+ x=sub['COLABORADOR'], y=sub['count'],
661
+ name=s, marker_color=color,
662
+ hovertemplate='<b>%{x}</b><br>' + s + ': %{y}<extra></extra>',
663
+ ))
664
+ fig.update_layout(**PLOTLY_LAYOUT, barmode='stack', showlegend=True)
665
+ fig.update_xaxes(title_text='')
666
+ fig.update_yaxes(title_text='Projetos')
667
+ return fig
668
+
669
+
670
+ @callback(
671
+ Output('chart-colab-qualidade', 'figure'),
672
+ Input('filter-mes', 'value'),
673
+ Input('filter-colab', 'value'),
674
+ Input('filter-tipo', 'value'),
675
+ Input('filter-status', 'value'),
676
+ )
677
+ def chart_colab_qualidade(mes, colab, tipo, status):
678
+ df = filter_df(mes, colab, tipo, status)
679
+ df_q = df[df['COLABORADOR'].notna() & df['INDICE_QUALIDADE'].notna()]
680
+ if df_q.empty:
681
+ return go.Figure().update_layout(**PLOTLY_LAYOUT)
682
+ stats = df_q.groupby('COLABORADOR')['INDICE_QUALIDADE'].agg(['mean', 'count']).reset_index()
683
+ stats = stats[stats['count'] >= 2].sort_values('mean', ascending=True)
684
+
685
+ fig = go.Figure(go.Bar(
686
+ x=stats['mean'], y=stats['COLABORADOR'],
687
+ orientation='h',
688
+ marker=dict(
689
+ color=stats['mean'],
690
+ colorscale=[[0, '#7f1d1d'], [0.5, COLORS['warning']], [1, COLORS['success']]],
691
+ cmin=0, cmax=100,
692
+ line=dict(width=0),
693
+ ),
694
+ text=[f"{v:.0f}%" for v in stats['mean']],
695
+ textposition='outside',
696
+ textfont=dict(size=11, color=COLORS['text']),
697
+ hovertemplate='<b>%{y}</b><br>Índice Qualidade: %{x:.1f}%<extra></extra>',
698
+ ))
699
+ fig.update_layout(**PLOTLY_LAYOUT, showlegend=False)
700
+ fig.update_xaxes(title_text='Índice de Qualidade (%)', range=[0, 115])
701
+ fig.update_yaxes(title_text='')
702
+ return fig
703
+
704
+
705
+ @callback(
706
+ Output('chart-tempo', 'figure'),
707
+ Input('filter-mes', 'value'),
708
+ Input('filter-colab', 'value'),
709
+ Input('filter-tipo', 'value'),
710
+ Input('filter-status', 'value'),
711
+ )
712
+ def chart_tempo(mes, colab, tipo, status):
713
+ df = filter_df(mes, colab, tipo, status)
714
+ df_t = df['TEMPO_EXECUCAO_DIAS'].dropna()
715
+ if df_t.empty:
716
+ return go.Figure().update_layout(**PLOTLY_LAYOUT)
717
+
718
+ fig = go.Figure()
719
+ fig.add_trace(go.Histogram(
720
+ x=df_t,
721
+ nbinsx=20,
722
+ marker_color=COLORS['primary'],
723
+ marker_line_color=COLORS['bg'],
724
+ marker_line_width=1,
725
+ opacity=0.85,
726
+ name='Projetos',
727
+ hovertemplate='%{x} dias: %{y} projetos<extra></extra>',
728
+ ))
729
+ # Linha mediana
730
+ med = df_t.median()
731
+ fig.add_vline(x=med, line_dash='dash', line_color=COLORS['warning'],
732
+ annotation_text=f'Mediana: {med:.0f}d',
733
+ annotation_font_color=COLORS['warning'],
734
+ annotation_position='top right')
735
+ fig.update_layout(**PLOTLY_LAYOUT, showlegend=False)
736
+ fig.update_xaxes(title_text='Dias')
737
+ fig.update_yaxes(title_text='NΒΊ Projetos')
738
+ return fig
739
+
740
+
741
+ @callback(
742
+ Output('chart-versoes', 'figure'),
743
+ Input('filter-mes', 'value'),
744
+ Input('filter-colab', 'value'),
745
+ Input('filter-tipo', 'value'),
746
+ Input('filter-status', 'value'),
747
+ )
748
+ def chart_versoes(mes, colab, tipo, status):
749
+ df = filter_df(mes, colab, tipo, status)
750
+ df_v = df['QTD_VERSOES'].dropna().astype(int)
751
+ counts = df_v.value_counts().sort_index().reset_index()
752
+ counts.columns = ['versoes', 'count']
753
+
754
+ fig = go.Figure(go.Bar(
755
+ x=counts['versoes'].astype(str),
756
+ y=counts['count'],
757
+ marker_color=[COLORS['success'], COLORS['warning'], COLORS['danger'], COLORS['purple']],
758
+ marker_line_width=0,
759
+ hovertemplate='<b>%{x} versΓ£o(Γ΅es)</b><br>%{y} projetos<extra></extra>',
760
+ ))
761
+ fig.update_layout(**PLOTLY_LAYOUT, showlegend=False)
762
+ fig.update_xaxes(title_text='NΒΊ VersΓ΅es')
763
+ fig.update_yaxes(title_text='Projetos')
764
+ return fig
765
+
766
+
767
+ @callback(
768
+ Output('chart-revisoes', 'figure'),
769
+ Input('filter-mes', 'value'),
770
+ Input('filter-colab', 'value'),
771
+ Input('filter-tipo', 'value'),
772
+ Input('filter-status', 'value'),
773
+ )
774
+ def chart_revisoes(mes, colab, tipo, status):
775
+ df = filter_df(mes, colab, tipo, status)
776
+ df_r = df['QTD_REVISOES'].dropna().astype(int)
777
+ counts = df_r.value_counts().sort_index().reset_index()
778
+ counts.columns = ['revisoes', 'count']
779
+
780
+ palette = [COLORS['success'], COLORS['teal'], COLORS['warning'],
781
+ COLORS['danger'], COLORS['purple'], COLORS['accent'],
782
+ COLORS['gold'], COLORS['muted']]
783
+ colors = [palette[i % len(palette)] for i in range(len(counts))]
784
+
785
+ fig = go.Figure(go.Bar(
786
+ x=counts['revisoes'].astype(str),
787
+ y=counts['count'],
788
+ marker_color=colors,
789
+ marker_line_width=0,
790
+ hovertemplate='<b>%{x} revisΓ£o(Γ΅es)</b><br>%{y} projetos<extra></extra>',
791
+ ))
792
+ fig.update_layout(**PLOTLY_LAYOUT, showlegend=False)
793
+ fig.update_xaxes(title_text='NΒΊ RevisΓ΅es')
794
+ fig.update_yaxes(title_text='Projetos')
795
+ return fig
796
+
797
+
798
+ @callback(
799
+ Output('chart-erros', 'figure'),
800
+ Input('filter-mes', 'value'),
801
+ Input('filter-colab', 'value'),
802
+ Input('filter-tipo', 'value'),
803
+ Input('filter-status', 'value'),
804
+ )
805
+ def chart_erros(mes, colab, tipo, status):
806
+ df = filter_df(mes, colab, tipo, status)
807
+ df_c = df[df['COLABORADOR_PROD_V0'].notna()]
808
+ df_c = df_c.groupby('COLABORADOR_PROD_V0').agg(
809
+ visuais=('PCT_ERROS_VISUAIS_V0', 'mean'),
810
+ macro=('PCT_ERROS_MACRO_V0', 'mean'),
811
+ ).dropna(how='all').reset_index()
812
+ df_c = df_c.sort_values('visuais', ascending=False).head(8)
813
+
814
+ if df_c.empty:
815
+ return go.Figure().update_layout(**PLOTLY_LAYOUT)
816
+
817
+ fig = go.Figure()
818
+ fig.add_trace(go.Bar(
819
+ name='Erros Visuais (%)',
820
+ x=df_c['COLABORADOR_PROD_V0'],
821
+ y=df_c['visuais'],
822
+ marker_color=COLORS['warning'],
823
+ marker_line_width=0,
824
+ hovertemplate='<b>%{x}</b><br>Erros Visuais: %{y:.1f}%<extra></extra>',
825
+ ))
826
+ fig.add_trace(go.Bar(
827
+ name='Erros Macro (%)',
828
+ x=df_c['COLABORADOR_PROD_V0'],
829
+ y=df_c['macro'],
830
+ marker_color=COLORS['danger'],
831
+ marker_line_width=0,
832
+ hovertemplate='<b>%{x}</b><br>Erros Macro: %{y:.1f}%<extra></extra>',
833
+ ))
834
+ fig.update_layout(**PLOTLY_LAYOUT, barmode='group', showlegend=True)
835
+ fig.update_xaxes(title_text='')
836
+ fig.update_yaxes(title_text='Taxa de Erros (%)')
837
+ return fig
838
+
839
+
840
+ @callback(
841
+ Output('chart-qualidade-hist', 'figure'),
842
+ Input('filter-mes', 'value'),
843
+ Input('filter-colab', 'value'),
844
+ Input('filter-tipo', 'value'),
845
+ Input('filter-status', 'value'),
846
+ )
847
+ def chart_qualidade_hist(mes, colab, tipo, status):
848
+ df = filter_df(mes, colab, tipo, status)
849
+ df_q = df['INDICE_QUALIDADE'].dropna()
850
+ if df_q.empty:
851
+ return go.Figure().update_layout(**PLOTLY_LAYOUT)
852
+
853
+ fig = go.Figure()
854
+ fig.add_trace(go.Histogram(
855
+ x=df_q,
856
+ nbinsx=15,
857
+ marker=dict(
858
+ color=df_q,
859
+ colorscale=[[0, '#7f1d1d'], [0.5, COLORS['warning']], [1, COLORS['success']]],
860
+ cmin=0, cmax=100,
861
+ line=dict(color=COLORS['bg'], width=1),
862
+ ),
863
+ hovertemplate='Índice %{x}%: %{y} projetos<extra></extra>',
864
+ ))
865
+ mean_q = df_q.mean()
866
+ fig.add_vline(x=mean_q, line_dash='dash', line_color=COLORS['accent'],
867
+ annotation_text=f'MΓ©dia: {mean_q:.0f}%',
868
+ annotation_font_color=COLORS['accent'],
869
+ annotation_position='top left')
870
+ fig.update_layout(**PLOTLY_LAYOUT, showlegend=False)
871
+ fig.update_xaxes(title_text='Índice de Qualidade (%)')
872
+ fig.update_yaxes(title_text='NΒΊ Projetos')
873
+ return fig
874
+
875
+
876
+ # ─────────────────────────────────────────────────────────────────────────────
877
+ # 7. ARRANQUE
878
+ # ─────────────────────────────────────────────────────────────────────────────
879
+
880
+ # Expor o servidor Flask subjacente β€” necessΓ‘rio para o gunicorn
881
+ server = app.server
882
+
883
+ if __name__ == '__main__':
884
+ app.run(host='0.0.0.0', port=7860, debug=False)
dashboard_executivo.zip ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d2f77888b15edaa8e1fcdf2e8b9005463c05add52f97abf4804fb0ba911cfddf
3
+ size 7125
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ dash==2.18.1
2
+ dash-bootstrap-components==1.6.0
3
+ plotly==5.22.0
4
+ pandas==2.2.2
5
+ numpy==1.26.4
6
+ gunicorn==22.0.0
tarefasss_datas_corrigidas_final.csv ADDED
The diff for this file is too large to render. See raw diff