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 # <-- Ajout ici | |
| except ImportError: | |
| # Fallback si l'import direct échoue (structure de dossier simple) | |
| import kyc_form | |
| import map_dashboard # <-- Ajout ici | |
| # ============================================================================== | |
| # 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" | |
| ] | |
| # Injection CSS : Design Gotham (Palantir) & Space Grotesk | |
| st.markdown(""" | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;700&display=swap'); | |
| .stApp { background-color: #10161A; color: #F5F8FA; font-family: 'Space Grotesk', sans-serif; } | |
| [data-testid="stSidebar"] { background-color: #182026; border-right: 1px solid #2B95D6; } | |
| /* Boutons Cobalt */ | |
| div.stButton > button:first-child { | |
| background-color: #2B95D6 !important; | |
| color: white !important; | |
| border: none !important; | |
| font-weight: 700; | |
| text-transform: uppercase; | |
| width: 100%; | |
| transition: 0.2s; | |
| } | |
| div.stButton > button:hover { background-color: #1F78B4 !important; } | |
| h1, h2, h3 { font-family: 'Space Grotesk', sans-serif !important; color: #2B95D6 !important; } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # ============================================================================== | |
| # 2. 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() | |
| # ============================================================================== | |
| # 3. 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}-2025-{next_id:04d}" #2025 a changé pour 2026 plus tard | |
| # ============================================================================== | |
| # 4. 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() | |
| menu = st.sidebar.radio("NAVIGATION", | |
| ["TABLEAU DE BORD", "CARTE TACTIQUE","KYC CLIENTS"] | |
| ) | |
| # --- A. TABLEAU DE BORD --- | |
| if menu == "TABLEAU DE BORD": | |
| st.header("OBJECT EXPLORER") | |
| st.success(f"✅ Liaison Directe établie avec '{SHEET_NAME}'") | |
| col1, col2, col3 = st.columns(3) | |
| col1.metric("CAPITAL DEHORS", "1.2M XOF", "▲ 5%") | |
| col2.metric("FLUX ATTENDU (J+7)", "450k XOF", "▼ 2%") | |
| col3.metric("SCORE LIQUIDITÉ", "8.5/10") | |
| st.divider() | |
| st.subheader("VUE ONTOLOGIQUE : CLIENTS") | |
| df_clients = get_data_from_sheet("Clients_KYC") | |
| if not df_clients.empty: | |
| st.dataframe(df_clients, use_container_width=True) | |
| else: | |
| st.info("ℹ️ L'onglet 'Clients_KYC' est vide ou n'existe pas encore.") | |
| # --- B. CODE DU MODULE CARTE TACTIQUE --- | |
| elif menu == "CARTE TACTIQUE": | |
| # Appel du nouveau module | |
| map_dashboard.show_map_dashboard(client, SHEET_NAME) | |
| # --- C. CODE DU MODULE KYC --- | |
| elif menu == "KYC CLIENTS": | |
| # APPEL DU MODULE EXTERNE | |
| # C'est ici que la magie opère : on passe le client et la fonction ID au module | |
| kyc_form.show_kyc_form(client, SHEET_NAME, generate_ontology_id) | |