import os import io import unicodedata import pandas as pd import streamlit as st import plotly.express as px # ----------------------------- # Configuración de la página # ----------------------------- st.set_page_config( page_title="Employee Count Dashboard", page_icon="📊", layout="wide", ) # ----------------------------- # Utilidades # ----------------------------- def normalize(s: str) -> str: s = unicodedata.normalize("NFKD", s).encode("ascii", "ignore").decode("utf-8", "ignore") return " ".join(s.lower().split()) def find_target_column(df: pd.DataFrame, target="extskhis_emp full name") -> str | None: norm_map = {col: normalize(col) for col in df.columns} for col, norm in norm_map.items(): if norm == normalize(target): return col candidates = [c for c, n in norm_map.items() if "full" in n and "name" in n] return candidates[0] if candidates else None @st.cache_data(show_spinner=False) def load_csv(file_obj) -> pd.DataFrame: return pd.read_csv(file_obj) @st.cache_data(show_spinner=False) def load_sample(sample_path: str) -> pd.DataFrame | None: if os.path.exists(sample_path): return pd.read_csv(sample_path) return None def pretty_number(n: int) -> str: return f"{n:,}".replace(",", " ") # ----------------------------- # Estilos (UI) # ----------------------------- CUSTOM_CSS = """ """ st.markdown(CUSTOM_CSS, unsafe_allow_html=True) # ----------------------------- # Sidebar (carga de datos) # ----------------------------- st.sidebar.title("⚙️ Configuración") uploaded = st.sidebar.file_uploader("Sube tu archivo CSV", type=["csv"]) sample_note = st.sidebar.empty() df = None source_label = "" if uploaded is not None: try: data_bytes = uploaded.getvalue() df = load_csv(io.BytesIO(data_bytes)) source_label = f"Fuente: Archivo subido — **{uploaded.name}**" except Exception as e: st.sidebar.error(f"Error al leer el CSV: {e}") else: df_sample = load_sample("data/sample.csv") if df_sample is not None: df = df_sample source_label = "Fuente: `data/sample.csv` (muestra)" sample_note.info("No subiste archivo. Mostrando ejemplo.") else: sample_note.warning("No subiste archivo y no existe `data/sample.csv`. Sube un CSV para continuar.") # ----------------------------- # UI principal # ----------------------------- st.title("📊 Employee Count Dashboard", anchor=False) if df is None or df.empty: st.info("Sube un CSV con la columna **EXTSKHIS_EMP FULL NAME** para ver el dashboard.") st.stop() target_col = find_target_column(df, "EXTSKHIS_EMP FULL NAME") if target_col is None: st.error("No se encontró la columna requerida.") st.write("Columnas detectadas:", list(df.columns)) st.stop() df[target_col] = df[target_col].astype(str).str.strip() # ----------------------------- # Controles # ----------------------------- with st.sidebar: st.divider() st.subheader("Filtros") search = st.text_input("Filtrar por nombre", placeholder="Ej: Maria, Juan...") min_count = st.number_input("Mínimo de ocurrencias", min_value=1, value=1, step=1) top_n = st.slider("Mostrar Top N", min_value=5, max_value=100, value=20, step=5) sort_mode = st.radio("Orden", ["Por conteo (desc)", "Alfabético (A→Z)"], index=0) df_filtered = df if search: s = search.lower() df_filtered = df[df[target_col].str.lower().str.contains(s, na=False)] counts = df_filtered.groupby(target_col, dropna=False).size().reset_index(name="Count") counts = counts[counts["Count"] >= min_count] if sort_mode == "Por conteo (desc)": counts = counts.sort_values("Count", ascending=False) else: counts = counts.sort_values(target_col, ascending=True) counts_top = counts.head(top_n) # ----------------------------- # KPIs # ----------------------------- c1, c2, c3 = st.columns(3) with c1: st.markdown("