import pandas as pd import numpy as np import plotly.graph_objects as go import plotly.express as px from dash import Dash, dcc, html, Input, Output, callback import dash_bootstrap_components as dbc import warnings warnings.filterwarnings('ignore') # ───────────────────────────────────────────────────────────────────────────── # 1. CARREGAMENTO E PREPARAÇÃO DOS DADOS # ───────────────────────────────────────────────────────────────────────────── df_raw = pd.read_csv( 'tarefasss_datas_corrigidas_final.csv', sep=';', encoding='latin-1' ) df_raw = df_raw[df_raw.iloc[:, 0].notna()].copy() df_raw.columns = [ 'SUB_CIP','PROJETO','DESIGNACAO','TIPO','RB_STATUS','COLABORADOR','OBSERVACOES', 'DATA_ADJ_CLIENTE','DATA_LIMITE_SLA','TEMPO_EXECUCAO_DIAS','PCT_SLA', 'DATA_ENTREGA_V0','DATA_1_REENTREGA_V1','DATA_2_REENTREGA_V2','DATA_3_REENTREGA_V3', 'DATA_VALIDACAO','QTD_VERSOES','TAXA_REJEICAO_VERSOES','TAXA_ACEITACAO_VERSOES', 'QTD_REVISOES','INDICE_QUALIDADE','COLABORADOR_PROD_V0','DATA_INICIO_PROD_V0', 'DATA_FIM_PROD_V0','COLABORADOR_QC_V0','DATA_INICIO_QC_V0','DATA_FIM_QC_V0', 'PCT_ERROS_VISUAIS_V0','PCT_ERROS_MACRO_V0','TAXA_APROVACAO_INTERNA_V0', 'COLABORADOR_PROD_V1','DATA_INICIO_PROD_V1','DATA_FIM_PROD_V1','COLABORADOR_QC_V1', 'DATA_INICIO_QC_V1','DATA_FIM_QC_V1','PCT_ERROS_VISUAIS_V1','PCT_ERROS_MACRO_V1', 'TAXA_ERRO_GLOBAL_V1','COLABORADOR_PROD_V2','DATA_INICIO_PROD_V2','DATA_FIM_PROD_V2', 'COLABORADOR_QC_V2','DATA_INICIO_QC_V2','DATA_FIM_QC_V2','PCT_ERROS_VISUAIS_V2', 'PCT_ERROS_MACRO_V2','TAXA_ERRO_GLOBAL_V2','RESPOSTA_CLIENTE','DATA_RESPOSTA' ] # Conversões numéricas for col in ['TEMPO_EXECUCAO_DIAS','QTD_VERSOES','QTD_REVISOES']: df_raw[col] = pd.to_numeric( df_raw[col].astype(str).str.replace(',', '.').str.strip(), errors='coerce' ) for col in ['PCT_SLA','INDICE_QUALIDADE','TAXA_REJEICAO_VERSOES','TAXA_ACEITACAO_VERSOES', 'PCT_ERROS_VISUAIS_V0','PCT_ERROS_MACRO_V0','TAXA_APROVACAO_INTERNA_V0']: df_raw[col] = pd.to_numeric( df_raw[col].astype(str).str.replace('%','').str.replace(',','.').str.strip(), errors='coerce' ) # Datas for col in ['DATA_ADJ_CLIENTE','DATA_LIMITE_SLA','DATA_ENTREGA_V0','DATA_VALIDACAO']: df_raw[col] = pd.to_datetime(df_raw[col], dayfirst=True, errors='coerce') df_raw['MES_ADJ'] = df_raw['DATA_ADJ_CLIENTE'].dt.to_period('M').astype(str) df_raw['MES_LABEL'] = df_raw['DATA_ADJ_CLIENTE'].dt.strftime('%b %Y') # Normalizar colaborador df_raw['COLABORADOR'] = df_raw['COLABORADOR'].str.strip().str.upper() df_raw['COLABORADOR'] = df_raw['COLABORADOR'].replace({'STEPHANIE FARIA': 'STEPHANIE FARIA'}) # Classificação SLA df_raw['SLA_OK'] = df_raw['PCT_SLA'].apply( lambda x: 'Dentro do SLA' if pd.notna(x) and x <= 100 else ('Fora do SLA' if pd.notna(x) else 'N/D') ) # Status simplificado para agrupamento status_map = { '08 PROJETO VALIDADO': 'Validado', '13 FATURADO': 'Faturado', '07 VALIDAÇO ORANGE': 'Validação Orange', '12 CANCELADO': 'Cancelado', '09 TRABALHOS EM CURSO': 'Em Curso', '11.1 AGUARDA RT': 'Aguarda RT', '03 POR INICIAR CQ': 'Por Iniciar CQ', '02.1 PROJETO PENDENTE CLIENTE': 'Pendente Cliente', '03.3 CQ SOGETREL': 'CQ Sogetrel', '06.4 AGUARDA PMV+DT': 'Aguarda PMV+DT', '06.2 AGUARDA DEVIS': 'Aguarda Devis', '02.3 PROJETO EM CURSO': 'Projeto em Curso', } df_raw['STATUS_LABEL'] = df_raw['RB_STATUS'].map(status_map).fillna(df_raw['RB_STATUS']) # ───────────────────────────────────────────────────────────────────────────── # 2. PALETA E TEMA # ───────────────────────────────────────────────────────────────────────────── COLORS = { 'bg': '#0D1117', 'card': '#161B22', 'card2': '#1C2128', 'border': '#30363D', 'primary': '#2563EB', 'accent': '#3B82F6', 'success': '#10B981', 'warning': '#F59E0B', 'danger': '#EF4444', 'text': '#E6EDF3', 'muted': '#8B949E', 'gold': '#F59E0B', 'purple': '#8B5CF6', 'teal': '#14B8A6', } STATUS_COLORS = { 'Validado': '#10B981', 'Faturado': '#2563EB', 'Validação Orange': '#F59E0B', 'Cancelado': '#EF4444', 'Em Curso': '#8B5CF6', 'Aguarda RT': '#14B8A6', 'Por Iniciar CQ': '#6366F1', 'Pendente Cliente': '#F97316', 'CQ Sogetrel': '#EC4899', 'Aguarda PMV+DT': '#84CC16', 'Aguarda Devis': '#06B6D4', 'Projeto em Curso': '#A78BFA', } PLOTLY_LAYOUT = dict( paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)', font=dict(family='Inter, Segoe UI, sans-serif', color=COLORS['text'], size=12), margin=dict(l=10, r=10, t=30, b=10), legend=dict( bgcolor='rgba(22,27,34,0.8)', bordercolor=COLORS['border'], borderwidth=1, font=dict(size=11), ), xaxis=dict( gridcolor=COLORS['border'], gridwidth=0.5, linecolor=COLORS['border'], tickfont=dict(size=11), ), yaxis=dict( gridcolor=COLORS['border'], gridwidth=0.5, linecolor=COLORS['border'], tickfont=dict(size=11), ), ) # ───────────────────────────────────────────────────────────────────────────── # 3. HELPERS # ───────────────────────────────────────────────────────────────────────────── def kpi_card(title, value, subtitle=None, color=COLORS['primary'], icon='📊'): return dbc.Col( html.Div([ html.Div([ html.Span(icon, style={'fontSize': '22px', 'marginRight': '8px'}), html.Span(title, style={ 'fontSize': '11px', 'fontWeight': '600', 'color': COLORS['muted'], 'textTransform': 'uppercase', 'letterSpacing': '0.08em' }), ], style={'display': 'flex', 'alignItems': 'center', 'marginBottom': '8px'}), html.Div(value, style={ 'fontSize': '32px', 'fontWeight': '700', 'color': color, 'lineHeight': '1', }), html.Div(subtitle or '', style={ 'fontSize': '11px', 'color': COLORS['muted'], 'marginTop': '6px' }), ], style={ 'background': COLORS['card'], 'border': f'1px solid {COLORS["border"]}', 'borderTop': f'3px solid {color}', 'borderRadius': '8px', 'padding': '18px 20px', 'height': '100%', }), xs=12, sm=6, md=3, ) def section_title(text): return html.Div(text, style={ 'fontSize': '13px', 'fontWeight': '700', 'color': COLORS['muted'], 'textTransform': 'uppercase', 'letterSpacing': '0.1em', 'marginBottom': '12px', 'paddingBottom': '8px', 'borderBottom': f'1px solid {COLORS["border"]}', }) def chart_card(children, title=None, height=None): style = { 'background': COLORS['card'], 'border': f'1px solid {COLORS["border"]}', 'borderRadius': '8px', 'padding': '16px', 'marginBottom': '16px', } if height: style['height'] = height content = [] if title: content.append(html.Div(title, style={ 'fontSize': '13px', 'fontWeight': '600', 'color': COLORS['text'], 'marginBottom': '12px', })) content.append(children) return html.Div(content, style=style) # ───────────────────────────────────────────────────────────────────────────── # 4. APP # ───────────────────────────────────────────────────────────────────────────── app = Dash( __name__, external_stylesheets=[ dbc.themes.BOOTSTRAP, 'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap', ], title='Dashboard Executivo — Gestão de Projetos', suppress_callback_exceptions=True, ) # ── Filtros disponíveis ─────────────────────────────────────────────────────── meses_sorted = sorted(df_raw['MES_ADJ'].dropna().unique()) colaboradores = sorted(df_raw['COLABORADOR'].dropna().unique()) tipos = sorted(df_raw['TIPO'].dropna().unique()) # ───────────────────────────────────────────────────────────────────────────── # 5. LAYOUT # ───────────────────────────────────────────────────────────────────────────── app.layout = html.Div([ # ── HEADER ──────────────────────────────────────────────────────────────── html.Div([ dbc.Container([ dbc.Row([ dbc.Col([ html.Div([ html.Span('⬡', style={'color': COLORS['primary'], 'fontSize': '24px', 'marginRight': '10px'}), html.Span('GESTÃO DE PROJETOS', style={ 'fontSize': '18px', 'fontWeight': '700', 'color': COLORS['text'], 'letterSpacing': '0.05em', }), html.Span(' · Dashboard Executivo', style={ 'fontSize': '14px', 'color': COLORS['muted'], 'marginLeft': '8px', }), ], style={'display': 'flex', 'alignItems': 'center'}), ], md=8), dbc.Col([ html.Div('Set 2025 – Fev 2026', style={ 'textAlign': 'right', 'color': COLORS['muted'], 'fontSize': '12px', 'paddingTop': '4px', }), ], md=4), ], align='center'), ], fluid=True), ], style={ 'background': COLORS['card'], 'borderBottom': f'1px solid {COLORS["border"]}', 'padding': '14px 0', 'position': 'sticky', 'top': '0', 'zIndex': '1000', }), # ── FILTROS ─────────────────────────────────────────────────────────────── dbc.Container([ html.Div([ dbc.Row([ dbc.Col([ html.Label('Período', style={'fontSize': '11px', 'color': COLORS['muted'], 'fontWeight': '600'}), dcc.Dropdown( id='filter-mes', options=[{'label': m, 'value': m} for m in meses_sorted], multi=True, placeholder='Todos os meses', style={'fontSize': '12px'}, className='dark-dropdown', ), ], md=3), dbc.Col([ html.Label('Colaborador', style={'fontSize': '11px', 'color': COLORS['muted'], 'fontWeight': '600'}), dcc.Dropdown( id='filter-colab', options=[{'label': c, 'value': c} for c in colaboradores], multi=True, placeholder='Todos', style={'fontSize': '12px'}, ), ], md=3), dbc.Col([ html.Label('Tipo de Projeto', style={'fontSize': '11px', 'color': COLORS['muted'], 'fontWeight': '600'}), dcc.Dropdown( id='filter-tipo', options=[{'label': t, 'value': t} for t in tipos], multi=True, placeholder='Todos', style={'fontSize': '12px'}, ), ], md=3), dbc.Col([ html.Label('Status', style={'fontSize': '11px', 'color': COLORS['muted'], 'fontWeight': '600'}), dcc.Dropdown( id='filter-status', options=[{'label': s, 'value': s} for s in sorted(df_raw['STATUS_LABEL'].unique())], multi=True, placeholder='Todos', style={'fontSize': '12px'}, ), ], md=3), ]), ], style={ 'background': COLORS['card2'], 'border': f'1px solid {COLORS["border"]}', 'borderRadius': '8px', 'padding': '14px 16px', 'marginTop': '16px', 'marginBottom': '16px', }), # ── KPIs ────────────────────────────────────────────────────────────── html.Div(id='kpi-row', style={'marginBottom': '16px'}), # ── LINHA 1: Evolução mensal + Distribuição por Status ──────────────── dbc.Row([ dbc.Col( chart_card( dcc.Graph(id='chart-evolucao', config={'displayModeBar': False}, style={'height': '300px'}), title='Evolução Mensal de Projetos Adjudicados' ), md=7 ), dbc.Col( chart_card( dcc.Graph(id='chart-status', config={'displayModeBar': False}, style={'height': '300px'}), title='Distribuição por Status' ), md=5 ), ]), # ── LINHA 2: Distribuição por Tipo + Resposta Cliente ───────────────── dbc.Row([ dbc.Col( chart_card( dcc.Graph(id='chart-tipo', config={'displayModeBar': False}, style={'height': '300px'}), title='Projetos por Tipo' ), md=5 ), dbc.Col( chart_card( dcc.Graph(id='chart-resposta', config={'displayModeBar': False}, style={'height': '300px'}), title='Resposta do Cliente (OK vs NOK)' ), md=3 ), dbc.Col( chart_card( dcc.Graph(id='chart-sla', config={'displayModeBar': False}, style={'height': '300px'}), title='Cumprimento de SLA' ), md=4 ), ]), # ── LINHA 3: Performance Colaboradores ──────────────────────────────── dbc.Row([ dbc.Col( chart_card( dcc.Graph(id='chart-colab-volume', config={'displayModeBar': False}, style={'height': '320px'}), title='Volume de Projetos por Colaborador' ), md=6 ), dbc.Col( chart_card( dcc.Graph(id='chart-colab-qualidade', config={'displayModeBar': False}, style={'height': '320px'}), title='Índice de Qualidade Médio por Colaborador' ), md=6 ), ]), # ── LINHA 4: Tempo Execução + Versões ───────────────────────────────── dbc.Row([ dbc.Col( chart_card( dcc.Graph(id='chart-tempo', config={'displayModeBar': False}, style={'height': '300px'}), title='Distribuição do Tempo de Execução (dias)' ), md=6 ), dbc.Col( chart_card( dcc.Graph(id='chart-versoes', config={'displayModeBar': False}, style={'height': '300px'}), title='Nº de Versões por Projeto' ), md=3 ), dbc.Col( chart_card( dcc.Graph(id='chart-revisoes', config={'displayModeBar': False}, style={'height': '300px'}), title='Nº de Revisões por Projeto' ), md=3 ), ]), # ── LINHA 5: Erros QC ───────────────────────────────────────────────── dbc.Row([ dbc.Col( chart_card( dcc.Graph(id='chart-erros', config={'displayModeBar': False}, style={'height': '300px'}), title='Taxa de Erros QC — Visuais vs Macro (V0)' ), md=6 ), dbc.Col( chart_card( dcc.Graph(id='chart-qualidade-hist', config={'displayModeBar': False}, style={'height': '300px'}), title='Distribuição do Índice de Qualidade' ), md=6 ), ]), # ── RODAPÉ ──────────────────────────────────────────────────────────── html.Div([ html.Span('Dashboard Executivo · Gestão de Projetos', style={'color': COLORS['muted'], 'fontSize': '11px'}), html.Span(' · Dados: Set 2025 – Fev 2026', style={'color': COLORS['border'], 'fontSize': '11px'}), ], style={ 'textAlign': 'center', 'padding': '24px 0 16px', 'borderTop': f'1px solid {COLORS["border"]}', 'marginTop': '8px', }), ], fluid=True, style={'maxWidth': '1400px'}), ], style={ 'background': COLORS['bg'], 'minHeight': '100vh', 'fontFamily': 'Inter, Segoe UI, sans-serif', 'color': COLORS['text'], }) # ───────────────────────────────────────────────────────────────────────────── # 6. CALLBACKS # ───────────────────────────────────────────────────────────────────────────── def filter_df(mes, colab, tipo, status): df = df_raw.copy() if mes: df = df[df['MES_ADJ'].isin(mes)] if colab: df = df[df['COLABORADOR'].isin(colab)] if tipo: df = df[df['TIPO'].isin(tipo)] if status: df = df[df['STATUS_LABEL'].isin(status)] return df @callback( Output('kpi-row', 'children'), Input('filter-mes', 'value'), Input('filter-colab', 'value'), Input('filter-tipo', 'value'), Input('filter-status', 'value'), ) def update_kpis(mes, colab, tipo, status): df = filter_df(mes, colab, tipo, status) total = len(df) validados = len(df[df['STATUS_LABEL'].isin(['Validado', 'Faturado', 'Validação Orange'])]) pct_valid = f"{validados/total*100:.0f}%" if total else "—" sla_ok = len(df[df['SLA_OK'] == 'Dentro do SLA']) pct_sla = f"{sla_ok/total*100:.0f}%" if total else "—" iq_mean = df['INDICE_QUALIDADE'].mean() iq_str = f"{iq_mean:.0f}%" if pd.notna(iq_mean) else "—" tempo_med = df['TEMPO_EXECUCAO_DIAS'].median() tempo_str = f"{tempo_med:.0f} dias" if pd.notna(tempo_med) else "—" cancelados = len(df[df['STATUS_LABEL'] == 'Cancelado']) resp_ok = len(df[df['RESPOSTA_CLIENTE'] == 'OK']) resp_total = len(df[df['RESPOSTA_CLIENTE'].isin(['OK', 'NOK'])]) pct_resp = f"{resp_ok/resp_total*100:.0f}%" if resp_total else "—" return dbc.Row([ kpi_card('Total de Projetos', f"{total:,}", 'no período selecionado', COLORS['primary'], '📁'), kpi_card('Taxa de Conclusão', pct_valid, f'{validados} projetos concluídos', COLORS['success'], '✅'), kpi_card('Cumprimento SLA', pct_sla, f'{sla_ok} dentro do prazo', COLORS['teal'], '⏱'), kpi_card('Índice de Qualidade', iq_str, 'média dos projetos avaliados', COLORS['gold'], '⭐'), kpi_card('Tempo Mediano', tempo_str, 'tempo de execução (dias)', COLORS['purple'], '📅'), kpi_card('Aprovação Cliente', pct_resp, f'{resp_ok} OK de {resp_total} avaliados', COLORS['accent'], '👤'), kpi_card('Cancelamentos', f"{cancelados}", 'projetos cancelados', COLORS['danger'], '❌'), kpi_card('Colaboradores Ativos', str(df['COLABORADOR'].nunique()), 'no período', COLORS['muted'], '👥'), ], className='g-2 mb-3') @callback( Output('chart-evolucao', 'figure'), Input('filter-mes', 'value'), Input('filter-colab', 'value'), Input('filter-tipo', 'value'), Input('filter-status', 'value'), ) def chart_evolucao(mes, colab, tipo, status): df = filter_df(mes, colab, tipo, status) monthly = df.groupby('MES_ADJ').size().reset_index(name='count') monthly = monthly.sort_values('MES_ADJ') fig = go.Figure() fig.add_trace(go.Bar( x=monthly['MES_ADJ'], y=monthly['count'], marker_color=COLORS['primary'], marker_line_width=0, opacity=0.85, name='Projetos', hovertemplate='%{x}
%{y} projetos', )) fig.add_trace(go.Scatter( x=monthly['MES_ADJ'], y=monthly['count'], mode='lines+markers', line=dict(color=COLORS['accent'], width=2), marker=dict(size=6, color=COLORS['accent']), name='Tendência', hovertemplate='%{x}
%{y} projetos', )) fig.update_layout(**PLOTLY_LAYOUT, showlegend=True) return fig @callback( Output('chart-status', 'figure'), Input('filter-mes', 'value'), Input('filter-colab', 'value'), Input('filter-tipo', 'value'), Input('filter-status', 'value'), ) def chart_status(mes, colab, tipo, status): df = filter_df(mes, colab, tipo, status) counts = df['STATUS_LABEL'].value_counts().reset_index() counts.columns = ['status', 'count'] colors = [STATUS_COLORS.get(s, COLORS['muted']) for s in counts['status']] fig = go.Figure(go.Pie( labels=counts['status'], values=counts['count'], hole=0.55, marker=dict(colors=colors, line=dict(color=COLORS['bg'], width=2)), textinfo='percent', hovertemplate='%{label}
%{value} projetos (%{percent})', )) layout = dict(**PLOTLY_LAYOUT) layout['legend'] = dict( orientation='v', x=1.02, y=0.5, bgcolor='rgba(0,0,0,0)', font=dict(size=10), ) layout['annotations'] = [dict( text=f"{len(df)}
total", x=0.5, y=0.5, font_size=16, showarrow=False, font=dict(color=COLORS['text']), )] fig.update_layout(**layout) return fig @callback( Output('chart-tipo', 'figure'), Input('filter-mes', 'value'), Input('filter-colab', 'value'), Input('filter-tipo', 'value'), Input('filter-status', 'value'), ) def chart_tipo(mes, colab, tipo, status): df = filter_df(mes, colab, tipo, status) counts = df['TIPO'].value_counts().reset_index() counts.columns = ['tipo', 'count'] counts = counts.sort_values('count', ascending=True) fig = go.Figure(go.Bar( x=counts['count'], y=counts['tipo'], orientation='h', marker=dict( color=counts['count'], colorscale=[[0, '#1e3a5f'], [1, COLORS['primary']]], line=dict(width=0), ), hovertemplate='%{y}
%{x} projetos', )) fig.update_layout(**PLOTLY_LAYOUT, showlegend=False) fig.update_xaxes(title_text='') fig.update_yaxes(title_text='') return fig @callback( Output('chart-resposta', 'figure'), Input('filter-mes', 'value'), Input('filter-colab', 'value'), Input('filter-tipo', 'value'), Input('filter-status', 'value'), ) def chart_resposta(mes, colab, tipo, status): df = filter_df(mes, colab, tipo, status) df_r = df[df['RESPOSTA_CLIENTE'].isin(['OK', 'NOK'])] counts = df_r['RESPOSTA_CLIENTE'].value_counts().reset_index() counts.columns = ['resp', 'count'] color_map = {'OK': COLORS['success'], 'NOK': COLORS['danger']} colors = [color_map.get(r, COLORS['muted']) for r in counts['resp']] fig = go.Figure(go.Pie( labels=counts['resp'], values=counts['count'], hole=0.6, marker=dict(colors=colors, line=dict(color=COLORS['bg'], width=3)), textinfo='percent+label', hovertemplate='%{label}
%{value} projetos', )) fig.update_layout(**PLOTLY_LAYOUT, showlegend=False) return fig @callback( Output('chart-sla', 'figure'), Input('filter-mes', 'value'), Input('filter-colab', 'value'), Input('filter-tipo', 'value'), Input('filter-status', 'value'), ) def chart_sla(mes, colab, tipo, status): df = filter_df(mes, colab, tipo, status) df_s = df[df['SLA_OK'] != 'N/D'] counts = df_s['SLA_OK'].value_counts().reset_index() counts.columns = ['sla', 'count'] color_map = {'Dentro do SLA': COLORS['success'], 'Fora do SLA': COLORS['danger']} colors = [color_map.get(s, COLORS['muted']) for s in counts['sla']] fig = go.Figure(go.Pie( labels=counts['sla'], values=counts['count'], hole=0.6, marker=dict(colors=colors, line=dict(color=COLORS['bg'], width=3)), textinfo='percent+label', hovertemplate='%{label}
%{value} projetos', )) fig.update_layout(**PLOTLY_LAYOUT, showlegend=False) return fig @callback( Output('chart-colab-volume', 'figure'), Input('filter-mes', 'value'), Input('filter-colab', 'value'), Input('filter-tipo', 'value'), Input('filter-status', 'value'), ) def chart_colab_volume(mes, colab, tipo, status): df = filter_df(mes, colab, tipo, status) df_c = df[df['COLABORADOR'].notna()] counts = df_c.groupby(['COLABORADOR', 'STATUS_LABEL']).size().reset_index(name='count') top_colabs = df_c['COLABORADOR'].value_counts().head(8).index.tolist() counts = counts[counts['COLABORADOR'].isin(top_colabs)] fig = go.Figure() for s, color in STATUS_COLORS.items(): sub = counts[counts['STATUS_LABEL'] == s] if not sub.empty: fig.add_trace(go.Bar( x=sub['COLABORADOR'], y=sub['count'], name=s, marker_color=color, hovertemplate='%{x}
' + s + ': %{y}', )) fig.update_layout(**PLOTLY_LAYOUT, barmode='stack', showlegend=True) fig.update_xaxes(title_text='') fig.update_yaxes(title_text='Projetos') return fig @callback( Output('chart-colab-qualidade', 'figure'), Input('filter-mes', 'value'), Input('filter-colab', 'value'), Input('filter-tipo', 'value'), Input('filter-status', 'value'), ) def chart_colab_qualidade(mes, colab, tipo, status): df = filter_df(mes, colab, tipo, status) df_q = df[df['COLABORADOR'].notna() & df['INDICE_QUALIDADE'].notna()] if df_q.empty: return go.Figure().update_layout(**PLOTLY_LAYOUT) stats = df_q.groupby('COLABORADOR')['INDICE_QUALIDADE'].agg(['mean', 'count']).reset_index() stats = stats[stats['count'] >= 2].sort_values('mean', ascending=True) fig = go.Figure(go.Bar( x=stats['mean'], y=stats['COLABORADOR'], orientation='h', marker=dict( color=stats['mean'], colorscale=[[0, '#7f1d1d'], [0.5, COLORS['warning']], [1, COLORS['success']]], cmin=0, cmax=100, line=dict(width=0), ), text=[f"{v:.0f}%" for v in stats['mean']], textposition='outside', textfont=dict(size=11, color=COLORS['text']), hovertemplate='%{y}
Índice Qualidade: %{x:.1f}%', )) fig.update_layout(**PLOTLY_LAYOUT, showlegend=False) fig.update_xaxes(title_text='Índice de Qualidade (%)', range=[0, 115]) fig.update_yaxes(title_text='') return fig @callback( Output('chart-tempo', 'figure'), Input('filter-mes', 'value'), Input('filter-colab', 'value'), Input('filter-tipo', 'value'), Input('filter-status', 'value'), ) def chart_tempo(mes, colab, tipo, status): df = filter_df(mes, colab, tipo, status) df_t = df['TEMPO_EXECUCAO_DIAS'].dropna() if df_t.empty: return go.Figure().update_layout(**PLOTLY_LAYOUT) fig = go.Figure() fig.add_trace(go.Histogram( x=df_t, nbinsx=20, marker_color=COLORS['primary'], marker_line_color=COLORS['bg'], marker_line_width=1, opacity=0.85, name='Projetos', hovertemplate='%{x} dias: %{y} projetos', )) # Linha mediana med = df_t.median() fig.add_vline(x=med, line_dash='dash', line_color=COLORS['warning'], annotation_text=f'Mediana: {med:.0f}d', annotation_font_color=COLORS['warning'], annotation_position='top right') fig.update_layout(**PLOTLY_LAYOUT, showlegend=False) fig.update_xaxes(title_text='Dias') fig.update_yaxes(title_text='Nº Projetos') return fig @callback( Output('chart-versoes', 'figure'), Input('filter-mes', 'value'), Input('filter-colab', 'value'), Input('filter-tipo', 'value'), Input('filter-status', 'value'), ) def chart_versoes(mes, colab, tipo, status): df = filter_df(mes, colab, tipo, status) df_v = df['QTD_VERSOES'].dropna().astype(int) counts = df_v.value_counts().sort_index().reset_index() counts.columns = ['versoes', 'count'] fig = go.Figure(go.Bar( x=counts['versoes'].astype(str), y=counts['count'], marker_color=[COLORS['success'], COLORS['warning'], COLORS['danger'], COLORS['purple']], marker_line_width=0, hovertemplate='%{x} versão(ões)
%{y} projetos', )) fig.update_layout(**PLOTLY_LAYOUT, showlegend=False) fig.update_xaxes(title_text='Nº Versões') fig.update_yaxes(title_text='Projetos') return fig @callback( Output('chart-revisoes', 'figure'), Input('filter-mes', 'value'), Input('filter-colab', 'value'), Input('filter-tipo', 'value'), Input('filter-status', 'value'), ) def chart_revisoes(mes, colab, tipo, status): df = filter_df(mes, colab, tipo, status) df_r = df['QTD_REVISOES'].dropna().astype(int) counts = df_r.value_counts().sort_index().reset_index() counts.columns = ['revisoes', 'count'] palette = [COLORS['success'], COLORS['teal'], COLORS['warning'], COLORS['danger'], COLORS['purple'], COLORS['accent'], COLORS['gold'], COLORS['muted']] colors = [palette[i % len(palette)] for i in range(len(counts))] fig = go.Figure(go.Bar( x=counts['revisoes'].astype(str), y=counts['count'], marker_color=colors, marker_line_width=0, hovertemplate='%{x} revisão(ões)
%{y} projetos', )) fig.update_layout(**PLOTLY_LAYOUT, showlegend=False) fig.update_xaxes(title_text='Nº Revisões') fig.update_yaxes(title_text='Projetos') return fig @callback( Output('chart-erros', 'figure'), Input('filter-mes', 'value'), Input('filter-colab', 'value'), Input('filter-tipo', 'value'), Input('filter-status', 'value'), ) def chart_erros(mes, colab, tipo, status): df = filter_df(mes, colab, tipo, status) df_c = df[df['COLABORADOR_PROD_V0'].notna()] df_c = df_c.groupby('COLABORADOR_PROD_V0').agg( visuais=('PCT_ERROS_VISUAIS_V0', 'mean'), macro=('PCT_ERROS_MACRO_V0', 'mean'), ).dropna(how='all').reset_index() df_c = df_c.sort_values('visuais', ascending=False).head(8) if df_c.empty: return go.Figure().update_layout(**PLOTLY_LAYOUT) fig = go.Figure() fig.add_trace(go.Bar( name='Erros Visuais (%)', x=df_c['COLABORADOR_PROD_V0'], y=df_c['visuais'], marker_color=COLORS['warning'], marker_line_width=0, hovertemplate='%{x}
Erros Visuais: %{y:.1f}%', )) fig.add_trace(go.Bar( name='Erros Macro (%)', x=df_c['COLABORADOR_PROD_V0'], y=df_c['macro'], marker_color=COLORS['danger'], marker_line_width=0, hovertemplate='%{x}
Erros Macro: %{y:.1f}%', )) fig.update_layout(**PLOTLY_LAYOUT, barmode='group', showlegend=True) fig.update_xaxes(title_text='') fig.update_yaxes(title_text='Taxa de Erros (%)') return fig @callback( Output('chart-qualidade-hist', 'figure'), Input('filter-mes', 'value'), Input('filter-colab', 'value'), Input('filter-tipo', 'value'), Input('filter-status', 'value'), ) def chart_qualidade_hist(mes, colab, tipo, status): df = filter_df(mes, colab, tipo, status) df_q = df['INDICE_QUALIDADE'].dropna() if df_q.empty: return go.Figure().update_layout(**PLOTLY_LAYOUT) fig = go.Figure() fig.add_trace(go.Histogram( x=df_q, nbinsx=15, marker=dict( color=df_q, colorscale=[[0, '#7f1d1d'], [0.5, COLORS['warning']], [1, COLORS['success']]], cmin=0, cmax=100, line=dict(color=COLORS['bg'], width=1), ), hovertemplate='Índice %{x}%: %{y} projetos', )) mean_q = df_q.mean() fig.add_vline(x=mean_q, line_dash='dash', line_color=COLORS['accent'], annotation_text=f'Média: {mean_q:.0f}%', annotation_font_color=COLORS['accent'], annotation_position='top left') fig.update_layout(**PLOTLY_LAYOUT, showlegend=False) fig.update_xaxes(title_text='Índice de Qualidade (%)') fig.update_yaxes(title_text='Nº Projetos') return fig # ───────────────────────────────────────────────────────────────────────────── # 7. ARRANQUE # ───────────────────────────────────────────────────────────────────────────── # Expor o servidor Flask subjacente — necessário para o gunicorn server = app.server if __name__ == '__main__': app.run(host='0.0.0.0', port=7860, debug=False)