# inventario_dashboard_final_v22_corrigido.py import pandas as pd import re from pathlib import Path import plotly.graph_objects as go import json # --- CONFIGURAÇÃO DE COLUNAS --- COLUNAS = { "nome": "NOME", "email_pessoal": "Email Pessoal Sendo Usado", "contato_colaborador": "Contato Colaborador", "contratacao": "Contratação", "status_contrato": "Status do Contrato", "cliente": "CLIENTE", "base": "BASE", "cargo": "CARGO", "responsavel_direto": "RESPONSÁVEL DIRETO", "contato_responsavel": "Contato do RESPONSÁVEL", "contato_1": "Momento do Contato Para Busca de Dados Contato 1", "contato_2": "Momento do Contato Para Busca de Dados Contato 2", "contato_3": "Momento do Contato Para Busca de Dados Contato 3", "subordinado": "Realmente e um Subordinado?", "usa_notebook": "Usa Notebook da Empresa", "email_corporativo_status": "Tem Email Corporativo?", "email_corporativo_endereco": "Email Corporativo", "tem_ad": "Tem conta no AD?", "maquina_registrada_status": "Maquina Registrada para o Cliente?", "maquina_registrada_nome": "Nome da Maquina Registrada", "tag_ativo": "Tag do Ativo", "numero_serie": "Rótulo de Serviço / Número de Série" } # --- Funções Auxiliares --- def normalizar_texto(texto): if isinstance(texto, str): return texto.strip().title() return texto def normalizar_telefone(numero): if not isinstance(numero, (str, int, float)): return None numeros_apenas = re.sub(r'\D', '', str(numero)) if len(numeros_apenas) == 11 and numeros_apenas.startswith('0'): numeros_apenas = numeros_apenas[1:] if len(numeros_apenas) in [10, 11]: return f"+55{numeros_apenas}" if len(numeros_apenas) == 12 and numeros_apenas.startswith('55'): return f"+{numeros_apenas}" if len(numeros_apenas) == 13 and numeros_apenas.startswith('55'): return f"+{numeros_apenas}" return None def extrair_sim_nao(texto): if not isinstance(texto, str): return "não informado" texto_limpo = texto.strip().lower() if texto_limpo.startswith('sim'): return 'sim' if texto_limpo.startswith('não') or texto_limpo.startswith('nao'): return 'não' return 'não informado' def formatar_contratacao(texto): if not isinstance(texto, str) or texto == "Não informado": return texto texto_lower = texto.lower() classe_css = "generico" if "pessoa juridica" in texto_lower or "pj" in texto_lower: classe_css = "pj" elif "clt" in texto_lower: classe_css = "clt" elif "estagio" in texto_lower or "estagiario" in texto_lower: classe_css = "estagio" return f"{texto}" def criar_grafico_pizza(valores, legendas, titulo, cores_personalizadas=None): valores_filtrados, legendas_filtradas, cores_filtradas = [], [], [] cores_originais = cores_personalizadas if cores_personalizadas else ['#28a745', '#dc3545', '#ffc107', '#6c757d'] for i, valor in enumerate(valores): if valor > 0: valores_filtrados.append(valor) legendas_filtradas.append(legendas[i]) cores_filtradas.append(cores_originais[i % len(cores_originais)]) if not valores_filtrados: return f"

{titulo}

Sem dados para exibir.

" fig = go.Figure(data=[go.Pie(labels=legendas_filtradas, values=valores_filtrados, hole=.4, marker_colors=cores_filtradas, textinfo='percent+value', insidetextfont=dict(color='white', size=14), hovertemplate="%{label}
%{value}
%{percent}")]) fig.update_layout(title_text=titulo, title_x=0.5, margin=dict(t=60, b=80, l=20, r=20), showlegend=True, legend=dict(orientation="h", yanchor="top", y=-0.1, xanchor="center", x=0.5), font=dict(family="Arial, sans-serif", size=14), paper_bgcolor='rgba(0,0,0,0)') return fig.to_html(full_html=False, include_plotlyjs=False, config={'displayModeBar': False, 'responsive': True}) def gerar_tabela_colaboradores(df_grupo): tabela_html = "
" for index, linha in df_grupo.iterrows(): filtros_da_linha = [key for key, value in linha.items() if key.startswith('filtro_') and value == True] data_filters_attr = ' '.join(filtros_da_linha) contato_colab = linha['Telefone Colaborador Normalizado'] contato_html = f'{contato_colab} ' if contato_colab else "Sem contato" contratacao_raw = linha.get(COLUNAS['contratacao']) contratacao = formatar_contratacao(contratacao_raw if pd.notna(contratacao_raw) else "Não informado") status_contrato = linha.get(COLUNAS['status_contrato'], "Não informado") status_contrato = status_contrato if pd.notna(status_contrato) else "Não informado" indicadores_html = "" info_notebook_original = linha.get(COLUNAS['usa_notebook']) classe_linha = "" if (isinstance(info_notebook_original, str) and 'saiu da empresa' in info_notebook_original.lower()) or \ (isinstance(status_contrato, str) and 'saiu da empresa' in status_contrato.lower()): classe_linha = " class='terminated'" if linha['filtro_em_conformidade']: indicadores_html = " Em conformidade" else: if linha.get('filtro_ativo_sem_ad') == True: indicadores_html += " Ativo sem Conta AD" if linha.get('filtro_ativo_nao_registrado') == True: indicadores_html += " Ativo Não Registrado" if linha['Usa Notebook (Valor)'] in ['não', 'não informado']: indicadores_html += " Ativo Próprio / Não Declarado" if pd.notna(linha['Email Pessoal']): indicadores_html += " Drive Externo" if linha['Tem Email Corp (Valor)'] == 'não': indicadores_html += " Sem E-mail Corp." if str(linha.get(COLUNAS["subordinado"])).lower() == 'não': indicadores_html += " Não Subordinado" is_cadastro_incompleto = linha['filtro_sem_gestor'] or linha['filtro_sem_contrato'] or linha['filtro_sem_status_contrato'] or linha['filtro_sem_cargo'] if is_cadastro_incompleto: indicadores_html += " Cadastro Incompleto" if not indicadores_html: indicadores_html = " Fora de Conformidade" detalhes_html = "" if 'terminated' in classe_linha: detalhes_html += "
Colaborador desligado da empresa.
" if pd.notna(linha.get(COLUNAS['cargo'])): detalhes_html += f"
{linha[COLUNAS['cargo']]}
" if linha['Usa Notebook (Valor)'] == 'sim': detalhes_html += "
Utiliza equipamento fornecido pela empresa.
" info_maquina = linha.get(COLUNAS['maquina_registrada_nome']) if pd.notna(info_maquina): detalhes_html += f"
Máquina: {info_maquina}
" elif isinstance(info_notebook_original, str) and 'usa pessoal' in info_notebook_original.lower(): detalhes_html += "
Colaborador utiliza equipamento pessoal.
" elif isinstance(info_notebook_original, str) and 'aguardando equipamento' in info_notebook_original.lower(): detalhes_html += "
Aguardando equipamento da empresa.
" elif isinstance(info_notebook_original, str) and info_notebook_original.strip().lower() == 'não': detalhes_html += f"
Informado que não utiliza notebook, sem detalhes adicionais.
" elif pd.notna(info_notebook_original): detalhes_html += f"
{info_notebook_original}
" if pd.notna(linha.get(COLUNAS['tag_ativo'])): detalhes_html += f"
Tag do Ativo: {linha[COLUNAS['tag_ativo']]}
" if pd.notna(linha.get(COLUNAS['numero_serie'])): detalhes_html += f"
N/S: {linha[COLUNAS['numero_serie']]}
" if pd.notna(linha.get(COLUNAS['email_corporativo_endereco'])): detalhes_html += f"
{linha[COLUNAS['email_corporativo_endereco']]}
" if pd.notna(linha['Email Pessoal']): detalhes_html += f"
{linha['Email Pessoal']}
" detalhes_final_html = detalhes_html if detalhes_html else "Sem informações adicionais." tabela_html += f"{linha[COLUNAS['nome']]}{contratacao}{status_contrato}{indicadores_html}{detalhes_final_html}" tabela_html += "
ColaboradorContrataçãoStatusContatoIndicadores de RiscoDetalhes
{contato_html}
" return tabela_html try: caminho_do_script = Path(__file__).parent nome_arquivo_excel = caminho_do_script / 'dados.xlsx' df = pd.read_excel(nome_arquivo_excel) df.replace(['N/A', 'Não informado', 'Sem Informação', 'Sem Informações'], pd.NA, inplace=True) for col_key in ["nome", "responsavel_direto"]: if COLUNAS[col_key] in df.columns: df[COLUNAS[col_key]] = df[COLUNAS[col_key]].apply(normalizar_texto) # --- Pré-Cálculos de Status e Riscos --- df['Telefone Colaborador Normalizado'] = df.get(COLUNAS["contato_colaborador"], pd.Series(dtype='str')).apply(normalizar_telefone) df['Usa Notebook (Valor)'] = df.get(COLUNAS["usa_notebook"], pd.Series(dtype='str')).apply(extrair_sim_nao) df['Tem Email Corp (Valor)'] = df.get(COLUNAS["email_corporativo_status"], pd.Series(dtype='str')).apply(extrair_sim_nao) df['Tem AD (Valor)'] = df.get(COLUNAS["tem_ad"], pd.Series(dtype='str')).apply(extrair_sim_nao) df['Maquina Reg (Valor)'] = df.get(COLUNAS["maquina_registrada_status"], pd.Series(dtype='str')).apply(extrair_sim_nao) if COLUNAS["email_pessoal"] in df.columns: df['Email Pessoal'] = df[COLUNAS["email_pessoal"]].apply(lambda x: x if isinstance(x, str) and '@' in x else None) else: df['Email Pessoal'] = None # --- DEFINIÇÃO DAS CONDIÇÕES DE FILTRO --- df['filtro_em_conformidade'] = (df['Usa Notebook (Valor)'] == 'sim') & \ (df.get(COLUNAS['maquina_registrada_nome'], pd.Series(dtype='str')).notna()) & \ (df['Tem Email Corp (Valor)'] == 'sim') & \ (df['Telefone Colaborador Normalizado'].notna()) & \ (df.get(COLUNAS['contratacao'], pd.Series(dtype='str')).notna()) & \ (df.get(COLUNAS['status_contrato'], pd.Series(dtype='str')).notna()) & \ (df['Email Pessoal'].isnull()) & \ (df.get(COLUNAS['responsavel_direto'], pd.Series(dtype='str')).notna()) df['filtro_fora_conformidade'] = ~df['filtro_em_conformidade'] df['filtro_incontactaveis'] = df['Telefone Colaborador Normalizado'].isnull() df['filtro_equip_proprio'] = df['Usa Notebook (Valor)'].isin(['não', 'não informado']) df['filtro_drives_externos'] = df['Email Pessoal'].notna() df['filtro_sem_email_corp'] = df['Tem Email Corp (Valor)'] == 'não' df['filtro_sem_gestor'] = df.get(COLUNAS['responsavel_direto'], pd.Series(dtype='str')).isnull() df['filtro_sem_contrato'] = df.get(COLUNAS['contratacao'], pd.Series(dtype='str')).isnull() df['filtro_sem_status_contrato'] = df.get(COLUNAS['status_contrato'], pd.Series(dtype='str')).isnull() df['filtro_sem_cargo'] = df.get(COLUNAS['cargo'], pd.Series(dtype='str')).isnull() df['filtro_ativo_sem_ad'] = (df['Usa Notebook (Valor)'] == 'sim') & (df['Tem AD (Valor)'] == 'não') df['filtro_ativo_nao_registrado'] = (df['Usa Notebook (Valor)'] == 'sim') & (df['Maquina Reg (Valor)'] == 'não') # --- Cálculos para KPIs Globais --- total_colaboradores = len(df) total_conformes = df['filtro_em_conformidade'].sum() total_nao_conformes = total_colaboradores - total_conformes perc_conformes = (total_conformes / total_colaboradores) * 100 if total_colaboradores > 0 else 0 perc_nao_conformes = (total_nao_conformes / total_colaboradores) * 100 if total_colaboradores > 0 else 0 total_sem_contato_valido = df['filtro_incontactaveis'].sum() risco_ativos_nao_declarados = df['filtro_equip_proprio'].sum() risco_drives_externos = df['filtro_drives_externos'].sum() risco_sem_email_corp = df['filtro_sem_email_corp'].sum() risco_sem_gestor = df['filtro_sem_gestor'].sum() risco_sem_contratacao = df['filtro_sem_contrato'].sum() risco_sem_status_contrato = df['filtro_sem_status_contrato'].sum() risco_sem_cargo = df['filtro_sem_cargo'].sum() risco_sem_ad_count = df['filtro_ativo_sem_ad'].sum() risco_maquina_nao_reg_count = df['filtro_ativo_nao_registrado'].sum() cond_risco_alto = df['filtro_incontactaveis'] | df['filtro_equip_proprio'] | df['filtro_drives_externos'] | df['filtro_sem_email_corp'] total_risco_alto = cond_risco_alto.sum() cond_risco_medio = df['filtro_sem_gestor'] | df['filtro_sem_contrato'] | df['filtro_sem_status_contrato'] | df['filtro_sem_cargo'] total_risco_medio = cond_risco_medio.sum() cond_risco_baixo = df['filtro_ativo_sem_ad'] | df['filtro_ativo_nao_registrado'] total_risco_baixo = cond_risco_baixo.sum() risco_inconsistencia_total = (df['filtro_ativo_sem_ad'] | df['filtro_ativo_nao_registrado']).sum() if risco_inconsistencia_total > 0: perc_risco_sem_ad = (risco_sem_ad_count / risco_inconsistencia_total) * 100 perc_risco_maquina_nao_reg = (risco_maquina_nao_reg_count / risco_inconsistencia_total) * 100 else: perc_risco_sem_ad, perc_risco_maquina_nao_reg = 0, 0 # --- Geração das Seções por Gestor --- html_secoes_gestores = "" if COLUNAS["responsavel_direto"] in df.columns: lista_gestores = sorted([g for g in df[COLUNAS["responsavel_direto"]].dropna().unique() if g]) for gestor in lista_gestores: df_equipe = df[df[COLUNAS["responsavel_direto"]] == gestor].copy() contato_gestor_raw = df_equipe.iloc[0].get(COLUNAS["contato_responsavel"]) contato_gestor_norm = normalizar_telefone(contato_gestor_raw) contato_gestor_html = f'{contato_gestor_norm} ' if contato_gestor_norm else "Sem contato" tentativas_contato = [] for col_key in ["contato_1", "contato_2", "contato_3"]: if COLUNAS[col_key] in df_equipe.columns and pd.notna(df_equipe.iloc[0].get(COLUNAS[col_key])): tentativas_contato.append(str(df_equipe.iloc[0][COLUNAS[col_key]])) contatos_realizados_html = f"
Tentativas de Contato ({len(tentativas_contato)}): {' | '.join(tentativas_contato) if tentativas_contato else 'Nenhuma registrada'}
" html_secoes_gestores += f"

Gestor: {gestor} ({contato_gestor_html})

{contatos_realizados_html}" total_equipe = len(df_equipe) notebook_counts_equipe = df_equipe['Usa Notebook (Valor)'].value_counts() sim_equipe, nao_equipe, ni_equipe = notebook_counts_equipe.get('sim', 0), notebook_counts_equipe.get('não', 0), notebook_counts_equipe.get('não informado', 0) grafico_equipe = criar_grafico_pizza([sim_equipe, nao_equipe, ni_equipe], ['Usa Notebook', 'Não Usa', 'Não Informado'], f"Uso de Notebook na Equipe ({total_equipe} Colaboradores)") tabela_equipe_html = gerar_tabela_colaboradores(df_equipe) html_secoes_gestores += f"
{grafico_equipe}{tabela_equipe_html}
" # FIX: Seção para colaboradores sem gestor df_sem_gestor = df[df[COLUNAS["responsavel_direto"]].isnull()].copy() if not df_sem_gestor.empty: html_secoes_gestores += "

Colaboradores Sem Gestor Definido

" tabela_sem_gestor_html = gerar_tabela_colaboradores(df_sem_gestor) html_secoes_gestores += f"
{tabela_sem_gestor_html}
" html_content = f""" Dashboard Consolidado de Gestão e Riscos

Dashboard Consolidado de Gestão e Riscos

Total de Colaboradores
{total_colaboradores}
Em Conformidade
{total_conformes} ({perc_conformes:.1f}%)
Fora de Conformidade
{total_nao_conformes} ({perc_nao_conformes:.1f}%)

Indicadores de Risco Alto ({total_risco_alto} Colaboradores)

Incontactáveis (TI)
{total_sem_contato_valido}
Equip. Próprio / Não Declarado
{risco_ativos_nao_declarados}
Drives Externos
{risco_drives_externos}
Sem E-mail Corporativo
{risco_sem_email_corp}

Indicadores de Risco Médio ({total_risco_medio} Colaboradores)

Sem Gestor Definido
{risco_sem_gestor}
Sem Tipo de Contrato
{risco_sem_contratacao}
Sem Status de Contrato
{risco_sem_status_contrato}
Sem Cargo Definido
{risco_sem_cargo}

Indicadores de Risco Baixo ({total_risco_baixo} Colaboradores)

Ativo sem Conta AD
{risco_sem_ad_count} ({perc_risco_sem_ad:.1f}%)
Ativo Não Registrado
{risco_maquina_nao_reg_count} ({perc_risco_maquina_nao_reg:.1f}%)

Análise por Equipe

{html_secoes_gestores}
""" with open('dashboard_interativo_final.html', 'w', encoding='utf-8') as f: f.write(html_content) print("Dashboard 'dashboard_interativo_final.html' com KPIs interativos e contagens corrigidas foi gerado com sucesso!") except (FileNotFoundError, ValueError) as e: print(f"ERRO: {e}") except Exception as e: print(f"Ocorreu um erro inesperado: {e}")