Spaces:
Running
Running
| import streamlit as st | |
| import pandas as pd | |
| import gspread | |
| from google.oauth2.service_account import Credentials | |
| import os | |
| import json | |
| import sys | |
| # Ajout du chemin pour trouver les modules | |
| sys.path.append(os.path.join(os.path.dirname(__file__), 'modules')) | |
| # IMPORT DU NOUVEAU MODULE | |
| try: | |
| from modules import kyc_form, map_dashboard, loans_engine, ml_dashboard, notifications, ontology_graph, repayments | |
| except ImportError: | |
| # Fallback si l'import direct échoue (structure de dossier simple) | |
| import kyc_form | |
| import map_dashboard | |
| import loans_engine | |
| import ml_dashboard | |
| import notifications | |
| import ontology_graph | |
| import repayments | |
| # ============================================================================== | |
| # 1. CONFIGURATION DU "DIRECT DRIVER" & DESIGN | |
| # ============================================================================== | |
| st.set_page_config( | |
| page_title="Vortex-Flux | Ontology", | |
| page_icon="🔺", | |
| layout="wide", | |
| initial_sidebar_state="expanded" | |
| ) | |
| # Nom exact de votre fichier Google Sheets (Doit être partagé avec l'email du bot) | |
| SHEET_NAME = "Vortex-Flux" | |
| # Scopes requis pour l'accès Google Drive/Sheets | |
| SCOPES = [ | |
| "https://www.googleapis.com/auth/spreadsheets", | |
| "https://www.googleapis.com/auth/drive" | |
| ] | |
| # ============================================================================== | |
| # 2. INJECTION CSS GOTHAM SURVEILLANCE - STYLE GLOBAL | |
| # ============================================================================== | |
| st.markdown(""" | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap'); | |
| /* === FOND GLOBAL - TONS SURVEILLANCE === */ | |
| .stApp { | |
| background: linear-gradient(135deg, #0d1117 0%, #161b22 50%, #1c2128 100%); | |
| color: #c9d1d9; | |
| font-family: 'Space Grotesk', sans-serif; | |
| } | |
| /* === SIDEBAR STYLE === */ | |
| [data-testid="stSidebar"] { | |
| background: linear-gradient(180deg, #161b22 0%, #1c2128 100%); | |
| border-right: 2px solid rgba(88, 166, 255, 0.3); | |
| } | |
| [data-testid="stSidebar"] [data-testid="stMarkdownContainer"] p { | |
| color: #8b949e !important; | |
| font-family: 'Space Grotesk', sans-serif !important; | |
| } | |
| [data-testid="stSidebar"] .stRadio label { | |
| color: #8b949e !important; | |
| font-family: 'Space Grotesk', sans-serif !important; | |
| font-size: 0.9rem; | |
| font-weight: 500; | |
| transition: all 0.2s ease; | |
| } | |
| [data-testid="stSidebar"] .stRadio label:hover { | |
| color: #58a6ff !important; | |
| } | |
| /* Titre sidebar */ | |
| [data-testid="stSidebar"] h1 { | |
| color: #58a6ff !important; | |
| font-family: 'Space Grotesk', sans-serif !important; | |
| font-weight: 700 !important; | |
| letter-spacing: 1px; | |
| text-transform: uppercase; | |
| } | |
| /* Caption sidebar */ | |
| [data-testid="stSidebar"] .caption { | |
| color: #6e7681 !important; | |
| font-size: 0.75rem; | |
| letter-spacing: 0.5px; | |
| } | |
| /* === APPLICATION SPACE GROTESK AUX CONTENUS TEXTUELS === */ | |
| .stApp h1, .stApp h2, .stApp h3, .stApp h4, .stApp h5, .stApp h6, | |
| .stApp p:not([data-testid]), | |
| .stMarkdown, | |
| .stText { | |
| font-family: 'Space Grotesk', sans-serif !important; | |
| } | |
| /* === HEADERS STYLE SURVEILLANCE === */ | |
| .stApp h1, .stApp h2, .stApp h3 { | |
| color: #58a6ff !important; | |
| font-family: 'Space Grotesk', sans-serif !important; | |
| font-weight: 500 !important; | |
| letter-spacing: 0.5px; | |
| text-shadow: none; | |
| } | |
| .stApp h1 { | |
| font-size: 1.8rem !important; | |
| border-bottom: 1px solid rgba(88, 166, 255, 0.2); | |
| padding-bottom: 12px; | |
| margin-bottom: 24px; | |
| } | |
| .stApp h2 { | |
| font-size: 1.3rem !important; | |
| color: #8b949e !important; | |
| } | |
| .stApp h3 { | |
| font-size: 1.1rem !important; | |
| } | |
| /* === METRICS CARDS - STYLE OPS CENTER === */ | |
| [data-testid="stMetric"] { | |
| background: rgba(22, 27, 34, 0.6); | |
| border: 1px solid rgba(48, 54, 61, 0.8); | |
| border-radius: 6px; | |
| padding: 16px; | |
| box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4); | |
| backdrop-filter: blur(8px); | |
| } | |
| [data-testid="stMetric"] label { | |
| color: #8b949e !important; | |
| font-size: 0.75rem !important; | |
| font-weight: 500 !important; | |
| text-transform: uppercase; | |
| letter-spacing: 0.8px; | |
| font-family: 'Space Grotesk', sans-serif !important; | |
| } | |
| [data-testid="stMetric"] [data-testid="stMetricValue"] { | |
| color: #c9d1d9 !important; | |
| font-size: 1.6rem !important; | |
| font-weight: 600 !important; | |
| text-shadow: none; | |
| font-family: 'Space Grotesk', sans-serif !important; | |
| } | |
| [data-testid="stMetric"] [data-testid="stMetricDelta"] { | |
| color: #58a6ff !important; | |
| font-size: 0.85rem !important; | |
| font-weight: 400; | |
| font-family: 'Space Grotesk', sans-serif !important; | |
| } | |
| /* === BOUTONS STYLE OPS === */ | |
| .stButton > button { | |
| background: rgba(22, 27, 34, 0.8); | |
| border: 1px solid rgba(88, 166, 255, 0.6); | |
| color: #58a6ff !important; | |
| font-weight: 600; | |
| font-size: 0.85rem; | |
| letter-spacing: 0.5px; | |
| border-radius: 4px; | |
| padding: 10px 20px; | |
| transition: all 0.2s ease; | |
| box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); | |
| text-transform: uppercase; | |
| font-family: 'Space Grotesk', sans-serif !important; | |
| } | |
| .stButton > button:hover { | |
| background: rgba(88, 166, 255, 0.15); | |
| border-color: rgba(88, 166, 255, 0.8); | |
| box-shadow: 0 0 12px rgba(88, 166, 255, 0.3); | |
| transform: translateY(-1px); | |
| } | |
| .stButton > button:active { | |
| transform: translateY(0); | |
| } | |
| /* === BOUTON RESET SPÉCIAL (ROUGE) === */ | |
| .reset-button > button { | |
| background: rgba(231, 76, 60, 0.1) !important; | |
| border: 1px solid rgba(231, 76, 60, 0.6) !important; | |
| color: #e74c3c !important; | |
| } | |
| .reset-button > button:hover { | |
| background: rgba(231, 76, 60, 0.2) !important; | |
| border-color: rgba(231, 76, 60, 0.8) !important; | |
| box-shadow: 0 0 12px rgba(231, 76, 60, 0.3) !important; | |
| } | |
| /* === DOWNLOAD BUTTON === */ | |
| .stDownloadButton > button { | |
| background: rgba(88, 166, 255, 0.1); | |
| border: 1px solid rgba(88, 166, 255, 0.4); | |
| color: #58a6ff !important; | |
| font-weight: 600; | |
| } | |
| .stDownloadButton > button:hover { | |
| background: rgba(88, 166, 255, 0.2); | |
| border-color: rgba(88, 166, 255, 0.6); | |
| box-shadow: 0 0 15px rgba(88, 166, 255, 0.3); | |
| } | |
| /* === EXPANDERS STYLE SURVEILLANCE === */ | |
| .streamlit-expanderHeader { | |
| background: rgba(22, 27, 34, 0.4); | |
| border-left: 2px solid rgba(88, 166, 255, 0.5); | |
| color: #8b949e !important; | |
| font-weight: 500; | |
| font-size: 0.9rem; | |
| letter-spacing: 0.3px; | |
| padding: 12px 16px; | |
| border-radius: 3px; | |
| font-family: 'Space Grotesk', sans-serif !important; | |
| } | |
| .streamlit-expanderHeader:hover { | |
| background: rgba(22, 27, 34, 0.6); | |
| border-left-color: rgba(88, 166, 255, 0.8); | |
| } | |
| /* === DATAFRAME STYLE === */ | |
| .stDataFrame { | |
| border: 1px solid rgba(48, 54, 61, 0.6); | |
| border-radius: 4px; | |
| overflow: hidden; | |
| font-size: 0.85rem; | |
| } | |
| .stDataFrame [data-testid="stTable"] { | |
| background: rgba(22, 27, 34, 0.6); | |
| } | |
| .stDataFrame thead tr th { | |
| background: rgba(48, 54, 61, 0.8) !important; | |
| color: #8b949e !important; | |
| font-weight: 600 !important; | |
| font-size: 0.8rem !important; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| font-family: 'Space Grotesk', sans-serif !important; | |
| } | |
| .stDataFrame tbody tr:hover { | |
| background: rgba(88, 166, 255, 0.05) !important; | |
| } | |
| /* === INFO/SUCCESS/WARNING/ERROR BOXES === */ | |
| .stAlert { | |
| background: rgba(22, 27, 34, 0.6); | |
| border: 1px solid rgba(48, 54, 61, 0.8); | |
| border-radius: 4px; | |
| color: #8b949e; | |
| font-size: 0.9rem; | |
| font-family: 'Space Grotesk', sans-serif !important; | |
| } | |
| .stAlert[data-baseweb="notification"] { | |
| border-left-width: 3px; | |
| } | |
| /* Info - Bleu */ | |
| .stAlert[kind="info"] { | |
| border-left-color: rgba(88, 166, 255, 0.6); | |
| background: rgba(88, 166, 255, 0.05); | |
| } | |
| /* Success - Vert */ | |
| .stAlert[kind="success"] { | |
| border-left-color: rgba(84, 189, 75, 0.6); | |
| background: rgba(84, 189, 75, 0.05); | |
| } | |
| /* Warning - Orange */ | |
| .stAlert[kind="warning"] { | |
| border-left-color: rgba(243, 156, 18, 0.6); | |
| background: rgba(243, 156, 18, 0.05); | |
| } | |
| /* Error - Rouge */ | |
| .stAlert[kind="error"] { | |
| border-left-color: rgba(231, 76, 60, 0.6); | |
| background: rgba(231, 76, 60, 0.05); | |
| } | |
| /* === TEXT INPUT & SELECT BOX === */ | |
| .stTextInput > div > div > input, | |
| .stSelectbox > div > div > div, | |
| .stTextArea textarea { | |
| background: rgba(22, 27, 34, 0.8) !important; | |
| border: 1px solid rgba(48, 54, 61, 0.8) !important; | |
| color: #c9d1d9 !important; | |
| font-family: 'Space Grotesk', sans-serif !important; | |
| border-radius: 4px; | |
| } | |
| .stTextInput > div > div > input:focus, | |
| .stSelectbox > div > div > div:focus, | |
| .stTextArea textarea:focus { | |
| border-color: rgba(88, 166, 255, 0.6) !important; | |
| box-shadow: 0 0 0 1px rgba(88, 166, 255, 0.3) !important; | |
| } | |
| /* === CHECKBOX === */ | |
| .stCheckbox label { | |
| color: #8b949e !important; | |
| font-weight: 500; | |
| font-size: 0.85rem; | |
| font-family: 'Space Grotesk', sans-serif !important; | |
| } | |
| .stCheckbox input:checked + span { | |
| background-color: #58a6ff !important; | |
| border-color: #58a6ff !important; | |
| } | |
| /* === RADIO BUTTONS MODIFIÉS EN PETITS CARRÉS === */ | |
| .stRadio label { | |
| color: #8b949e !important; | |
| font-family: 'Space Grotesk', sans-serif !important; | |
| } | |
| /* Transformer les boutons radio en carrés */ | |
| .stRadio [data-baseweb="radio"] > div:first-child { | |
| border-radius: 3px !important; /* Carré au lieu de cercle */ | |
| width: 16px !important; | |
| height: 16px !important; | |
| } | |
| /* Point intérieur des boutons radio (quand sélectionné) */ | |
| .stRadio [data-baseweb="radio"] > div:first-child > div { | |
| border-radius: 2px !important; /* Petit carré intérieur */ | |
| width: 8px !important; | |
| height: 8px !important; | |
| top: 4px !important; | |
| left: 4px !important; | |
| } | |
| /* Couleur du carré sélectionné */ | |
| .stRadio input:checked + div > div:first-child { | |
| background-color: #58a6ff !important; | |
| border-color: #58a6ff !important; | |
| } | |
| /* =========================================== */ | |
| /* RÈGLES SPÉCIFIQUES POUR SUPPRIMER LE SURLIGNAGE */ | |
| /* =========================================== */ | |
| /* Désactiver complètement la sélection de texte sur les éléments interactifs */ | |
| [data-testid="stSidebar"] * { | |
| -webkit-user-select: none !important; | |
| -moz-user-select: none !important; | |
| -ms-user-select: none !important; | |
| user-select: none !important; | |
| } | |
| /* Réactiver la sélection uniquement pour les champs de saisie */ | |
| [data-testid="stSidebar"] input, | |
| [data-testid="stSidebar"] textarea { | |
| -webkit-user-select: text !important; | |
| -moz-user-select: text !important; | |
| -ms-user-select: text !important; | |
| user-select: text !important; | |
| } | |
| /* Supprimer l'effet de sélection de texte (comme dans Word) */ | |
| [data-testid="stSidebar"] *::selection, | |
| [data-testid="stSidebar"] *::-moz-selection { | |
| background: transparent !important; | |
| color: inherit !important; | |
| } | |
| /* Règles spécifiques pour les boutons radio dans la sidebar */ | |
| [data-testid="stSidebar"] .stRadio > div > div[role="radiogroup"] { | |
| outline: none !important; | |
| box-shadow: none !important; | |
| } | |
| /* Supprimer les états de focus sur les boutons radio */ | |
| [data-testid="stSidebar"] .stRadio [role="radiogroup"]:focus, | |
| [data-testid="stSidebar"] .stRadio [role="radiogroup"]:focus-visible, | |
| [data-testid="stSidebar"] .stRadio [role="radiogroup"]:active { | |
| outline: none !important; | |
| box-shadow: none !important; | |
| border: none !important; | |
| } | |
| /* Supprimer le surlignage sur les labels radio */ | |
| [data-testid="stSidebar"] .stRadio label:focus, | |
| [data-testid="stSidebar"] .stRadio label:active, | |
| [data-testid="stSidebar"] .stRadio label:focus-visible { | |
| outline: none !important; | |
| box-shadow: none !important; | |
| background: transparent !important; | |
| } | |
| /* Cibler les éléments BaseWeb spécifiques des boutons radio */ | |
| [data-testid="stSidebar"] .stRadio [data-baseweb="radio"]:focus, | |
| [data-testid="stSidebar"] .stRadio [data-baseweb="radio"]:active, | |
| [data-testid="stSidebar"] .stRadio [data-baseweb="radio"]:focus-visible { | |
| outline: none !important; | |
| box-shadow: none !important; | |
| border: none !important; | |
| } | |
| /* Supprimer le tap highlight sur mobile */ | |
| [data-testid="stSidebar"] .stRadio > div, | |
| [data-testid="stSidebar"] .stRadio label { | |
| -webkit-tap-highlight-color: transparent !important; | |
| tap-highlight-color: transparent !important; | |
| } | |
| /* Supprimer l'effet de focus sur les éléments div des boutons radio */ | |
| [data-testid="stSidebar"] .stRadio div:focus, | |
| [data-testid="stSidebar"] .stRadio div:focus-visible, | |
| [data-testid="stSidebar"] .stRadio div:active { | |
| outline: none !important; | |
| box-shadow: none !important; | |
| } | |
| /* Règles globales pour supprimer les outlines */ | |
| *:focus, | |
| *:focus-visible, | |
| *:focus-within { | |
| outline: none !important; | |
| box-shadow: none !important; | |
| } | |
| /* Supprimer la sélection de texte globale */ | |
| ::selection { | |
| background: transparent !important; | |
| color: inherit !important; | |
| } | |
| ::-moz-selection { | |
| background: transparent !important; | |
| color: inherit !important; | |
| } | |
| /* Empêcher la sélection sur les éléments UI */ | |
| .stButton, | |
| .stRadio, | |
| .stCheckbox, | |
| [data-testid="stSidebar"], | |
| [data-testid="stSidebar"] *, | |
| .stTabs, | |
| h1, h2, h3, h4, h5, h6 { | |
| -webkit-user-select: none !important; | |
| -moz-user-select: none !important; | |
| -ms-user-select: none !important; | |
| user-select: none !important; | |
| } | |
| /* Autoriser la sélection dans les zones de texte */ | |
| .stTextInput input, | |
| .stTextArea textarea, | |
| .stDataFrame, | |
| p, span, li { | |
| -webkit-user-select: text !important; | |
| -moz-user-select: text !important; | |
| -ms-user-select: text !important; | |
| user-select: text !important; | |
| } | |
| /* Supprimer les bordures focus Streamlit */ | |
| [data-testid="stButton"] button:focus, | |
| [data-testid="stRadio"] *:focus, | |
| [data-testid="stCheckbox"] *:focus { | |
| outline: none !important; | |
| box-shadow: none !important; | |
| border: none !important; | |
| } | |
| /* Inputs gardent leur bordure normale au focus */ | |
| .stTextInput input:focus, | |
| .stSelectbox select:focus, | |
| .stTextArea textarea:focus { | |
| outline: none !important; | |
| box-shadow: none !important; | |
| border-color: rgba(48, 54, 61, 0.8) !important; | |
| } | |
| /* Supprimer le highlight au clic (active state) */ | |
| *:active { | |
| outline: none !important; | |
| -webkit-tap-highlight-color: transparent !important; | |
| } | |
| /* Pour mobile/tactile */ | |
| * { | |
| -webkit-tap-highlight-color: transparent !important; | |
| -webkit-touch-callout: none !important; | |
| } | |
| /* === DIVIDER === */ | |
| hr { | |
| border: none; | |
| height: 1px; | |
| background: rgba(48, 54, 61, 0.6); | |
| margin: 2rem 0; | |
| } | |
| /* === TABS === */ | |
| .stTabs [data-baseweb="tab-list"] { | |
| background: rgba(22, 27, 34, 0.4); | |
| border-bottom: 1px solid rgba(48, 54, 61, 0.8); | |
| } | |
| .stTabs [data-baseweb="tab"] { | |
| color: #8b949e !important; | |
| font-family: 'Space Grotesk', sans-serif !important; | |
| font-weight: 500; | |
| } | |
| .stTabs [data-baseweb="tab"]:hover { | |
| color: #58a6ff !important; | |
| background: rgba(88, 166, 255, 0.05); | |
| } | |
| .stTabs [aria-selected="true"] { | |
| color: #58a6ff !important; | |
| border-bottom: 2px solid #58a6ff !important; | |
| } | |
| /* === SCROLLBAR CUSTOMIZATION === */ | |
| ::-webkit-scrollbar { | |
| width: 8px; | |
| height: 8px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: rgba(13, 17, 23, 0.4); | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: rgba(48, 54, 61, 0.8); | |
| border-radius: 4px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: rgba(88, 166, 255, 0.3); | |
| } | |
| /* === SPINNER === */ | |
| .stSpinner > div { | |
| border-top-color: #58a6ff !important; | |
| } | |
| /* === PROGRESS BAR === */ | |
| .stProgress > div > div > div { | |
| background-color: #58a6ff !important; | |
| } | |
| /* === LIENS === */ | |
| a { | |
| color: #58a6ff !important; | |
| text-decoration: none; | |
| } | |
| a:hover { | |
| color: #1f78b4 !important; | |
| text-decoration: underline; | |
| } | |
| /* === CODE BLOCKS === */ | |
| code { | |
| background: rgba(22, 27, 34, 0.8) !important; | |
| color: #c9d1d9 !important; | |
| padding: 2px 6px; | |
| border-radius: 3px; | |
| font-family: 'Fira Code', monospace !important; | |
| } | |
| pre { | |
| background: rgba(22, 27, 34, 0.8) !important; | |
| border: 1px solid rgba(48, 54, 61, 0.6); | |
| border-radius: 4px; | |
| padding: 12px; | |
| } | |
| /* === COLONNES === */ | |
| [data-testid="column"] { | |
| background: transparent; | |
| padding: 8px; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # ============================================================================== | |
| # 3. MOTEUR DE CONNEXION (GSPREAD PUR) | |
| # ============================================================================== | |
| def get_gspread_client(): | |
| """Authentification robuste via gspread sans passer par st.connection""" | |
| raw_json = os.environ.get("GSHEETS_JSON") | |
| if not raw_json: | |
| st.error("🛑 SECRET MANQUANT : 'GSHEETS_JSON' est introuvable.") | |
| return None | |
| try: | |
| # Création du fichier temporaire sécurisé | |
| creds_dict = json.loads(raw_json) | |
| with open("temp_creds.json", "w") as f: | |
| json.dump(creds_dict, f) | |
| # Authentification Google | |
| credentials = Credentials.from_service_account_file("temp_creds.json", scopes=SCOPES) | |
| client = gspread.authorize(credentials) | |
| return client | |
| except Exception as e: | |
| st.error(f"⚠️ Erreur d'authentification GSpread : {e}") | |
| return None | |
| def get_data_from_sheet(worksheet_name): | |
| """Récupère les données d'un onglet sous forme de DataFrame""" | |
| client = get_gspread_client() | |
| if not client: return pd.DataFrame() | |
| try: | |
| # Ouverture du classeur par son nom | |
| sh = client.open(SHEET_NAME) | |
| # Sélection de l'onglet | |
| worksheet = sh.worksheet(worksheet_name) | |
| # Lecture des données | |
| data = worksheet.get_all_records() | |
| return pd.DataFrame(data) | |
| except gspread.WorksheetNotFound: | |
| # Si l'onglet n'existe pas encore, on renvoie vide sans planter | |
| return pd.DataFrame() | |
| except gspread.SpreadsheetNotFound: | |
| st.error(f"⚠️ Fichier Google Sheet '{SHEET_NAME}' introuvable. Vérifiez le nom et le partage.") | |
| return pd.DataFrame() | |
| except Exception as e: | |
| st.error(f"Erreur de lecture : {e}") | |
| return pd.DataFrame() | |
| # ============================================================================== | |
| # 4. LOGIQUE ONTOLOGIQUE (IDs) | |
| # ============================================================================== | |
| def generate_ontology_id(prefix, sheet_tab): | |
| df = get_data_from_sheet(sheet_tab) | |
| # Calcul ID : Nombre de lignes + 1 | |
| next_id = len(df) + 1 | |
| return f"{prefix}-2026-{next_id:04d}" #2026 a changé pour 2027 plus tard | |
| # ============================================================================== | |
| # 5. FONCTION DE RÉINITIALISATION COMPLÈTE | |
| # ============================================================================== | |
| def reset_application(): | |
| """ | |
| Réinitialise complètement l'application : | |
| - Vide le session_state | |
| - Vide tous les caches | |
| - Force un rechargement complet | |
| """ | |
| # 1. Sauvegarder les clés essentielles (si nécessaire) | |
| # Par exemple, si vous voulez garder certaines variables | |
| # keys_to_keep = [] | |
| # 2. Vider complètement le session_state | |
| for key in list(st.session_state.keys()): | |
| del st.session_state[key] | |
| # 3. Vider tous les caches de Streamlit | |
| st.cache_data.clear() | |
| st.cache_resource.clear() | |
| # 4. Force un rechargement complet | |
| st.rerun() | |
| # ============================================================================== | |
| # 6. INTERFACE UTILISATEUR | |
| # ============================================================================== | |
| client = get_gspread_client() | |
| if client: | |
| # --- BARRE LATÉRALE --- | |
| st.sidebar.title("🔺 VORTEX-FLUX") | |
| st.sidebar.caption("Jumeau Numérique & Ontologie") | |
| st.sidebar.divider() | |
| st.sidebar.markdown("### System") | |
| menu = st.sidebar.radio("Module", | |
| ["Dashboard", "Ontology Model", "Tracking Metrics","Actor Onboarding", "Init Loan", "Repayments"] | |
| ) | |
| st.sidebar.divider() | |
| # --- BOUTON DE RÉINITIALISATION --- | |
| st.sidebar.markdown("### Rerunning") | |
| col1, col2 = st.sidebar.columns([3, 1]) | |
| with col1: | |
| st.markdown("<small style='color: #6e7681;'>Réinitialiser l'application</small>", unsafe_allow_html=True) | |
| with col2: | |
| # Utilisation d'une clé unique et d'une classe CSS personnalisée | |
| if st.button("reset", key="reset_app_btn", help="Réinitialiser l'application à son état initial"): | |
| reset_application() | |
| # Version alternative avec un bouton plus visible : | |
| # st.sidebar.markdown("---") | |
| # if st.sidebar.button("🔄 RESET APPLICATION", key="reset_full", help="Réinitialiser complètement l'application"): | |
| # reset_application() | |
| # --- A. TABLEAU DE BORD --- | |
| if menu == "Dashboard": | |
| # --- AJOUTS VORTEX-FLUX (ML & NOTIFICATIONS) --- | |
| # 1.Appel direct du Dashboard ML (Le "Nouveau" Tableau de Bord) | |
| ml_dashboard.show_ml_features(client, SHEET_NAME) | |
| st.divider() | |
| # 2. Module de Notification (Bouton de vérification) | |
| notifications.verifier_et_notifier_echeances(client, SHEET_NAME) | |
| # --- B. CODE DU MODULE ONTOLOGY --- | |
| elif menu == "Ontology Model": | |
| ontology_graph.show_ontology_graph(client, SHEET_NAME) | |
| # --- C. CODE DU MODULE CARTE TACTIQUE --- | |
| elif menu == "Tracking Metrics": | |
| # Appel du nouveau module | |
| map_dashboard.show_map_dashboard(client, SHEET_NAME) | |
| # --- D. CODE DU MODULE KYC --- | |
| elif menu == "Actor Onboarding": | |
| # APPEL DU MODULE EXTERNE | |
| kyc_form.show_kyc_form(client, SHEET_NAME, generate_ontology_id) | |
| # --- E. CODE DU MODULE LOANS --- | |
| elif menu == "Init Loan": | |
| loans_engine.show_loans_engine(client, SHEET_NAME) | |
| # --- F. CODE DU MODULE Repayment --- | |
| elif menu == "Repayments": | |
| repayments.show_repayments_module(client, SHEET_NAME) | |
| else: | |
| st.error("🛑 Impossible de se connecter à Google Sheets. Vérifiez vos credentials.") |