""" USIS.PY Orchestrateur du module USIS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Responsabilites : - Chargement gspread + enrichissement silencieux DX_* puis EX_* - Navbar secondaire USIS via st.radio - CSS scope [data-testid="stMain"] => n'affecte JAMAIS la navbar principale Vortex-Flux dans la sidebar Streamlit - Dispatch vers Data_description.py et Data_exploration.py Integration streamlit_app.py : from modules import Usis elif menu == "USIS": Usis.show_usis_module(client, SHEET_NAME) """ import streamlit as st import pandas as pd try: from Analytics import Data_description as dd from Analytics import Data_exploration as de from Analytics import ML_Feature_Store_Analytics as mla except ImportError: import sys, os sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) from Analytics import Data_description as dd from Analytics import Data_exploration as de from Analytics import ML_Feature_Store_Analytics as mla _FONT = "JetBrains Mono, Courier New, monospace" _C = { "bg": "#0a0e12", "card": "#0f141a", "border": "rgba(80,100,120,0.25)", "accent": "#58a6ff", "ex": "#f39c12", "red": "#c0392b", "text": "#a8b8c8", "subtext": "#5a6a7a", } _MODES = ["U0 INTEGRITE", "U1 DESCRIPTION", "U2 EXPLORATION", "U3 ML FEATURE STORE"] _SECTIONS = [ ("CLIENTS", "Clients_KYC"), ("GARANTS", "Garants_KYC"), ("PRETS", "Prets_Master"), ("PRETS UPDATE", "Prets_Update"), ("REMBOURSEMENTS", "Remboursements"), ("AJUSTEMENTS", "Ajustements_Echeances"), ] _SHEETS = [s for _, s in _SECTIONS] # ── CSS ─────────────────────────────────────────────────────────────────────── # Scope critique : [data-testid="stMain"] garantit que ces regles # ne touchent JAMAIS les radio/elements de la navbar principale Vortex-Flux. # La sidebar Streamlit est dans section[data-testid="stSidebar"] ; # les colonnes USIS sont dans le contenu principal, hors de cette section. _NAV_CSS = f""" """ @st.cache_data(ttl=300, show_spinner=False) def _load_sheets(_client, sheet_name: str) -> dict: try: sh = _client.open(sheet_name) except Exception as e: st.error(f"[ USIS ] Impossible d'ouvrir '{sheet_name}' : {e}") return {s: pd.DataFrame() for s in _SHEETS} out = {} for tab in _SHEETS: try: ws = sh.worksheet(tab) data = ws.get_all_records() df = pd.DataFrame(data) if not df.empty: df.columns = [c.strip() for c in df.columns] out[tab] = df except Exception: out[tab] = pd.DataFrame() return out def _render_nav(sheets: dict) -> tuple: """ Navbar USIS via st.radio. Le CSS est injecte AVANT les colonnes dans show_usis_module. Retourne (mode: str, section_key: str). """ # ── Stats silencieuses ──────────────────────────────────────────────────── loaded = sum(1 for df in sheets.values() if isinstance(df, __import__('pandas').DataFrame) and not df.empty) dx_n = sum(pd.Index(df.columns.astype(str)).str.startswith("DX_").sum() for df in sheets.values() if isinstance(df, pd.DataFrame)) ex_n = sum(pd.Index(df.columns.astype(str)).str.startswith("EX_").sum() for df in sheets.values() if isinstance(df, pd.DataFrame)) # ── Radio MODE ──────────────────────────────────────────────────────────── # Pas de st.markdown wrapper : chaque markdown cree un bloc Streamlit # avec de la hauteur, ce qui pousserait le contenu vers le bas. mode_sel = st.radio( "MODE", _MODES, key="usis_mode", label_visibility="visible", ) # Derive la cle mode courte mode = "U0" if "U0" in mode_sel else "U1" if "U1" in mode_sel else "U2" # ── Radio SECTION (U1 et U2 seulement) ─────────────────────────────────── section_key = "CLIENTS" if mode in ("U1", "U2"): section_labels = [lbl for lbl, _ in _SECTIONS] labels_display = [ f"{lbl} [{len(sheets.get(sh, pd.DataFrame()))}]" if len(sheets.get(sh, pd.DataFrame())) > 0 else f"{lbl} [--]" for lbl, sh in _SECTIONS ] sel_display = st.radio( "SECTION", labels_display, key="usis_section", label_visibility="visible", ) idx = labels_display.index(sel_display) section_key = section_labels[idx] # ── Stats ───────────────────────────────────────────────────────────────── st.markdown( f'
' f'FEUILLES : {loaded}/{len(_SHEETS)}
' f'DX_* : {dx_n}
' f'EX_* : {ex_n}' f'
', unsafe_allow_html=True, ) return mode, section_key def show_usis_module(client, sheet_name: str): """ Appele depuis streamlit_app.py : from modules import Usis elif menu == "USIS": Usis.show_usis_module(client, SHEET_NAME) """ # Chargement en pleine largeur AVANT les colonnes (spinner visible) with st.spinner("[ USIS ] CHARGEMENT ET ENRICHISSEMENT EN COURS..."): raw = _load_sheets(client, sheet_name) sheets = dd.enrich_dx(raw) # 28 scores DX_* sheets = de.enrich_ex(sheets) # 27 scores EX_* sheets = mla.enrich_fl(sheets) # 23 features FL_* loaded = sum(1 for df in sheets.values() if isinstance(df, __import__('pandas').DataFrame) and not df.empty) if loaded == 0: st.error("[ USIS ] Aucune feuille chargee. Verifiez la connexion Google Sheets.") return # CSS injecte UNE FOIS ici, avant les colonnes, pour ne pas creer # de bloc Streamlit supplementaire dans nav_col qui decalerait le contenu. st.markdown(_NAV_CSS, unsafe_allow_html=True) # Layout : navbar (1.1) | gap (0.05) | contenu (4.5) nav_col, _, main_col = st.columns([1.1, 0.05, 4.5]) with nav_col: mode, section = _render_nav(sheets) with main_col: # En-tete a l'interieur de la colonne contenu uniquement st.markdown( f'
' f'MODULE USIS' f'
' f'

' f'SURVEILLANCE INTEGRITE SANTE

', unsafe_allow_html=True, ) fl_count = sum(c.startswith("FL_") for c in sheets.get("Clients_KYC", __import__("pandas").DataFrame()).columns) st.caption( "U0 Integrite | " "U1 Description (28 DX_*) | " "U2 Exploration (27 EX_*) | " f"U3 ML Feature Store ({fl_count} FL_*)" ) st.divider() if mode == "U0": dd.render_unit0(sheets) elif mode == "U1": dd.render_description(sheets, section) elif mode == "U2": filtered = de.render_filter_bar(sheets) st.markdown("
", unsafe_allow_html=True) de.render_exploration(filtered, section) elif mode == "U3": mla.render_feature_store(sheets)