Spaces:
Running
Running
| import streamlit as st | |
| import pandas as pd | |
| import plotly.express as px | |
| import plotly.graph_objects as go | |
| from datetime import datetime, timedelta | |
| import numpy as np | |
| import time | |
| def show_ml_features(client, sheet_name): | |
| # Fonction helper pour charger les données | |
| def get_data_from_sheet(sheet_name_tab): | |
| try: | |
| # Assurez-vous que l'objet 'client' et 'sheet_name' sont définis en amont dans votre script | |
| sh = client.open(sheet_name) | |
| ws = sh.worksheet(sheet_name_tab) | |
| return pd.DataFrame(ws.get_all_records()) | |
| except: | |
| return pd.DataFrame() | |
| # Chargement des données nécessaires | |
| # Note: 'client' et 'sheet_name' doivent être définis avant ce bloc | |
| df_prets_master = get_data_from_sheet("Prets_Master") | |
| df_prets_update = get_data_from_sheet("Prets_Update") | |
| df_capital_invest = get_data_from_sheet("Capital_Investissement") | |
| df_clients = get_data_from_sheet("Clients_KYC") | |
| # === En-tête === | |
| st.header("OBJECT EXPLORER") | |
| # Note: 'sheet_name' doit être défini | |
| st.success(f"✅ Liaison Directe établie") | |
| # === CALCULS POUR LES MÉTRIQUES === | |
| # 1. Capital de l'entreprise (somme de Capital_Investissement) | |
| capital_entreprise = 0 | |
| capital_variation = 0 | |
| if not df_capital_invest.empty and 'Capital' in df_capital_invest.columns: | |
| df_capital_invest['Capital_Num'] = pd.to_numeric( | |
| df_capital_invest['Capital'].astype(str).str.replace('FCFA', '').str.replace(' ', '').str.strip(), | |
| errors='coerce' | |
| ).fillna(0) | |
| capital_entreprise = df_capital_invest['Capital_Num'].sum() | |
| # Variation = dernier vs précédent | |
| if len(df_capital_invest) > 1: | |
| derniere_entree = df_capital_invest.iloc[-1]['Capital_Num'] | |
| avant_derniere = df_capital_invest.iloc[-2]['Capital_Num'] | |
| if avant_derniere > 0: | |
| capital_variation = ((derniere_entree - avant_derniere) / avant_derniere) * 100 | |
| # 2. Capital dehors (prêts non remboursés) | |
| capital_dehors = 0 | |
| if not df_prets_master.empty and 'Montant_Capital' in df_prets_master.columns: | |
| df_prets_master['Montant_Capital_Num'] = pd.to_numeric(df_prets_master['Montant_Capital'], errors='coerce').fillna(0) | |
| capital_dehors = df_prets_master[df_prets_master['Statut'] != 'TERMINE']['Montant_Capital_Num'].sum() | |
| # 3. Flux attendu (Montant_Total des prêts non remboursés) | |
| flux_attendu = 0 | |
| flux_variation = 0 | |
| if not df_prets_master.empty and 'Montant_Total' in df_prets_master.columns: | |
| df_prets_master['Montant_Total_Num'] = pd.to_numeric(df_prets_master['Montant_Total'], errors='coerce').fillna(0) | |
| flux_attendu = df_prets_master[df_prets_master['Statut'] != 'TERMINE']['Montant_Total_Num'].sum() | |
| # Variation = différence avec capital dehors | |
| if capital_dehors > 0: | |
| flux_variation = ((flux_attendu - capital_dehors) / capital_dehors) * 100 | |
| # 4. Score de liquidité | |
| score_liquidite = 0 | |
| score_tendance = "Stable" | |
| if capital_entreprise > 0: | |
| score_liquidite = (capital_dehors / capital_entreprise) * 10 | |
| score_liquidite = min(score_liquidite, 10) | |
| # Tendance basée sur le ratio | |
| ratio = (capital_dehors / capital_entreprise) * 100 | |
| if ratio > 70: | |
| score_tendance = "▲ Élevé" | |
| elif ratio > 40: | |
| score_tendance = "→ Moyen" | |
| else: | |
| score_tendance = "▼ Faible" | |
| # 5. Bénéfices nets 2026 | |
| benefices_nets = 0 | |
| benefices_variation = 0 | |
| if not df_prets_master.empty and 'Cout_Credit' in df_prets_master.columns: | |
| df_prets_master['Cout_Credit_Num'] = pd.to_numeric(df_prets_master['Cout_Credit'], errors='coerce').fillna(0) | |
| benefices_nets = df_prets_master['Cout_Credit_Num'].sum() | |
| # Simulation variation (peut être calculée vs mois précédent si date disponible) | |
| benefices_variation = 5.2 # Placeholder - à calculer avec historique | |
| # 6. Objectif 2026 | |
| objectif_2026 = 2_200_000 | |
| progression_objectif = (benefices_nets / objectif_2026) * 100 if objectif_2026 > 0 else 0 | |
| # 7. Reste à générer | |
| reste_a_generer = max(0, objectif_2026 - benefices_nets) | |
| reste_variation = -((reste_a_generer / objectif_2026) * 100) if objectif_2026 > 0 else 0 | |
| # 8. Capital total sorti | |
| capital_total_sorti = 0 | |
| nb_prets_total = 0 | |
| if not df_prets_master.empty and 'Montant_Capital' in df_prets_master.columns: | |
| # Convertir Montant_Capital en numérique si pas déjà fait | |
| if 'Montant_Capital_Num' not in df_prets_master.columns: | |
| df_prets_master['Montant_Capital_Num'] = pd.to_numeric(df_prets_master['Montant_Capital'], errors='coerce').fillna(0) | |
| # Prêts NON UPDATED - on prend de Prets_Master | |
| prets_non_updated = df_prets_master[df_prets_master['Statut'] != 'UPDATED'] | |
| nb_prets_total = len(prets_non_updated) | |
| capital_total_sorti = prets_non_updated['Montant_Capital_Num'].sum() | |
| # Prêts UPDATED - on prend depuis Prets_Update | |
| if not df_prets_update.empty and 'ID_Pret' in df_prets_update.columns: | |
| df_prets_update['Montant_Capital_Num'] = pd.to_numeric(df_prets_update['Montant_Capital'], errors='coerce').fillna(0) | |
| prets_updated_ids = df_prets_master[df_prets_master['Statut'] == 'UPDATED']['ID_Pret'].tolist() | |
| prets_updated_montants = df_prets_update[df_prets_update['ID_Pret'].isin(prets_updated_ids)] | |
| capital_total_sorti += prets_updated_montants['Montant_Capital_Num'].sum() | |
| nb_prets_total += len(prets_updated_montants) | |
| # Ajouter aussi les prêts qui sont UNIQUEMENT dans Prets_Update (nouveaux prêts) | |
| prets_update_nouveaux = df_prets_update[~df_prets_update['ID_Pret'].isin(df_prets_master['ID_Pret'].tolist())] | |
| capital_total_sorti += prets_update_nouveaux['Montant_Capital_Num'].sum() | |
| nb_prets_total += len(prets_update_nouveaux) | |
| # Ajoutez le ration bénéfice net sur total sortie | |
| # === AFFICHAGE DES MÉTRIQUES (2 lignes de 4) === | |
| # Ligne 1 | |
| col1, col2, col3, col4 = st.columns(4) | |
| col1.metric( | |
| "CAPITAL D'INVESTISSEMENT", | |
| f"{capital_entreprise:,.0f} XOF".replace(',', ' '), | |
| delta=f"{'▲' if capital_variation > 0 else '▼'} {abs(capital_variation):.1f}%" if capital_variation != 0 else "Stable" | |
| ) | |
| col2.metric( | |
| "CAPITAL DEHORS", | |
| f"{capital_dehors:,.0f} XOF".replace(',', ' '), | |
| delta=f"{(capital_dehors/capital_entreprise*100 if capital_entreprise > 0 else 0):.1f}% du capital investi" | |
| ) | |
| col3.metric( | |
| "FLUX ATTENDU", | |
| f"{flux_attendu:,.0f} XOF".replace(',', ' '), | |
| delta=f"{'▲' if flux_variation > 0 else '▼'} {abs(flux_variation):.1f}% vs capital" if flux_variation != 0 else "Stable" | |
| ) | |
| col4.metric( | |
| "LIQUIDITÉ UTILISÉE", | |
| f"{score_liquidite:.1f}/10", | |
| delta=score_tendance | |
| ) | |
| # Ligne 2 | |
| col5, col6, col7, col8 = st.columns(4) | |
| col5.metric( | |
| "BÉNÉFICES NETS 2026", | |
| f"{benefices_nets:,.0f} XOF".replace(',', ' '), | |
| delta=f"▲ {benefices_variation:.1f}%" if benefices_variation > 0 else "Stable" | |
| ) | |
| col6.metric( | |
| "OBJECTIF 2026", | |
| f"{objectif_2026:,.0f} XOF".replace(',', ' '), | |
| delta=f"{progression_objectif:.1f}% atteint" | |
| ) | |
| col7.metric( | |
| "RESTE À GÉNÉRER", | |
| f"{reste_a_generer:,.0f} XOF".replace(',', ' '), | |
| delta=f"▼ {abs(reste_variation):.1f}%" if reste_a_generer > 0 else "✓ Objectif atteint !" | |
| ) | |
| # Affichage de la métrique | |
| col8.metric( | |
| "CAPITAL TOTAL SORTI", | |
| f"{capital_total_sorti:,.0f} XOF".replace(',', ' '), | |
| delta=f"{nb_prets_total} prêt{'s' if nb_prets_total > 1 else ''} tot{'aux' if nb_prets_total > 1 else 'al'}" | |
| ) | |
| #======> ANALYSE MENSUELLE DES BÉNÉFICES <===== | |
| st.divider() | |
| st.subheader("ANALYSE MENSUELLE DES BÉNÉFICES 2026") | |
| # Préparation des données mensuelles | |
| if not df_prets_master.empty and 'Date_Deblocage' in df_prets_master.columns and 'Cout_Credit' in df_prets_master.columns: | |
| # Conversion des dates et montants | |
| df_prets_master['Date_Deblocage_dt'] = pd.to_datetime(df_prets_master['Date_Deblocage'], format='%d/%m/%Y', errors='coerce') | |
| df_prets_master['Cout_Credit_Num'] = pd.to_numeric(df_prets_master['Cout_Credit'], errors='coerce').fillna(0) | |
| # Filtrer uniquement l'année 2026 | |
| df_2026 = df_prets_master[df_prets_master['Date_Deblocage_dt'].dt.year == 2026].copy() | |
| if not df_2026.empty: | |
| # Créer la colonne Mois (numérique) | |
| df_2026['Mois'] = df_2026['Date_Deblocage_dt'].dt.month | |
| # CRÉER d'abord benefices_mensuels | |
| benefices_mensuels = df_2026.groupby('Mois')['Cout_Credit_Num'].sum().reset_index() | |
| benefices_mensuels.columns = ['Mois', 'Benefice'] | |
| # Ajouter les noms de mois | |
| mois_noms = {1: 'Janvier', 2: 'Février', 3: 'Mars', 4: 'Avril', | |
| 5: 'Mai', 6: 'Juin', 7: 'Juillet', 8: 'Août', | |
| 9: 'Septembre', 10: 'Octobre', 11: 'Novembre', 12: 'Décembre'} | |
| benefices_mensuels['Mois_Nom'] = benefices_mensuels['Mois'].map(mois_noms) | |
| # Calculer les variations mensuelles (% vs mois précédent) | |
| benefices_mensuels['Variation'] = benefices_mensuels['Benefice'].pct_change() * 100 | |
| benefices_mensuels['Variation'] = benefices_mensuels['Variation'].fillna(0) | |
| # Calculer le capital mensuel sorti (Montant_Capital) | |
| df_2026['Montant_Capital_Num'] = pd.to_numeric(df_2026['Montant_Capital'], errors='coerce').fillna(0) | |
| capital_mensuel = df_2026.groupby('Mois')['Montant_Capital_Num'].sum().reset_index() | |
| capital_mensuel.columns = ['Mois', 'Capital_Sorti'] | |
| # Fusionner avec les bénéfices mensuels | |
| benefices_mensuels = benefices_mensuels.merge(capital_mensuel, on='Mois', how='left') | |
| benefices_mensuels['Capital_Sorti'] = benefices_mensuels['Capital_Sorti'].fillna(0) | |
| # Graphique en barres groupées avec Plotly (pleine largeur) | |
| fig_mensuel = go.Figure() | |
| # Barres des bénéfices | |
| fig_mensuel.add_trace(go.Bar( | |
| x=benefices_mensuels['Mois_Nom'], | |
| y=benefices_mensuels['Benefice'], | |
| name='Bénéfices', | |
| marker=dict( | |
| color='#58a6ff', | |
| line=dict(color='rgba(139, 148, 158, 0.3)', width=1.5) | |
| ), | |
| text=benefices_mensuels['Benefice'].apply(lambda x: f"{x:,.0f}".replace(',', ' ')), | |
| textposition='outside', | |
| textfont=dict(size=11, color='#c9d1d9', family='Space Grotesk'), | |
| opacity=0.85, | |
| hovertemplate='<b>%{x}</b><br>Bénéfice: %{y:,.0f} XOF<extra></extra>' | |
| )) | |
| # Barres du capital sorti | |
| fig_mensuel.add_trace(go.Bar( | |
| x=benefices_mensuels['Mois_Nom'], | |
| y=benefices_mensuels['Capital_Sorti'], | |
| name='Capital Sorti', | |
| marker=dict( | |
| color='#f85149', | |
| line=dict(color='rgba(139, 148, 158, 0.3)', width=1.5) | |
| ), | |
| text=benefices_mensuels['Capital_Sorti'].apply(lambda x: f"{x:,.0f}".replace(',', ' ')), | |
| textposition='outside', | |
| textfont=dict(size=11, color='#c9d1d9', family='Space Grotesk'), | |
| opacity=0.85, | |
| hovertemplate='<b>%{x}</b><br>Capital Sorti: %{y:,.0f} XOF<extra></extra>' | |
| )) | |
| # Mise en forme | |
| fig_mensuel.update_layout( | |
| title={ | |
| 'text': 'Évolution des bénéfices et capital sorti mensuels (2026)', | |
| 'font': {'size': 16, 'color': '#c9d1d9', 'family': 'Space Grotesk'}, | |
| 'x': 0, | |
| 'xanchor': 'left' | |
| }, | |
| plot_bgcolor='rgba(13, 17, 23, 0.8)', | |
| paper_bgcolor='rgba(22, 27, 34, 0.3)', | |
| font={'color': '#8b949e', 'family': 'Space Grotesk', 'size': 12}, | |
| xaxis={ | |
| 'title': '', | |
| 'gridcolor': 'rgba(48, 54, 61, 0.3)', | |
| 'linecolor': 'rgba(48, 54, 61, 0.5)', | |
| 'tickfont': {'family': 'Space Grotesk', 'size': 13}, | |
| 'tickangle': -45 | |
| }, | |
| yaxis={ | |
| 'title': { | |
| 'text': 'Montant (XOF)', | |
| 'font': {'size': 13, 'family': 'Space Grotesk'} | |
| }, | |
| 'gridcolor': 'rgba(48, 54, 61, 0.3)', | |
| 'linecolor': 'rgba(48, 54, 61, 0.5)', | |
| 'tickfont': {'family': 'Space Grotesk', 'size': 12} | |
| }, | |
| barmode='group', | |
| showlegend=True, | |
| legend=dict( | |
| orientation='h', | |
| yanchor='bottom', | |
| y=1.02, | |
| xanchor='right', | |
| x=1, | |
| font={'family': 'Space Grotesk', 'size': 12, 'color': '#c9d1d9'} | |
| ), | |
| height=550, | |
| margin=dict(t=100, b=120, l=80, r=30) | |
| ) | |
| # === TREEMAP DES VARIATIONS MENSUELLES === | |
| st.markdown("<h3 style='font-size: 1.1rem; color: #8b949e; margin-top: 32px; margin-bottom: 16px;'>Répartition mensuelle des bénéfices</h3>", unsafe_allow_html=True) | |
| # Préparer les données pour le treemap (uniquement les mois avec bénéfices > 0) | |
| df_treemap = benefices_mensuels[benefices_mensuels['Benefice'] > 0].copy() | |
| if not df_treemap.empty: | |
| # Ajouter les pourcentages | |
| total_benefices = df_treemap['Benefice'].sum() | |
| df_treemap['Pourcentage'] = (df_treemap['Benefice'] / total_benefices * 100).round(1) | |
| # Créer des labels avec nom du mois + pourcentage | |
| df_treemap['Label'] = df_treemap.apply( | |
| lambda row: f"{row['Mois_Nom']}<br>{row['Pourcentage']:.1f}%", | |
| axis=1 | |
| ) | |
| # Créer le treemap | |
| fig_treemap = go.Figure(go.Treemap( | |
| labels=df_treemap['Label'], | |
| parents=[""] * len(df_treemap), # Tous au même niveau | |
| values=df_treemap['Benefice'], | |
| text=df_treemap['Benefice'].apply(lambda x: f"{x:,.0f} XOF".replace(',', ' ')), | |
| textposition="middle center", | |
| textfont=dict(size=11, color='#ffffff', family='Space Grotesk', weight=600), | |
| marker=dict( | |
| colors=df_treemap['Benefice'], | |
| colorscale=[ | |
| [0, '#1c2128'], # Très faible - gris foncé | |
| [0.2, '#30363d'], # Faible - gris | |
| [0.4, '#58a6ff'], # Moyen - bleu principal | |
| [0.6, '#79c0ff'], # Bon - bleu clair | |
| [0.8, '#a5d6ff'], # Très bon - bleu très clair | |
| [1, '#c9d1d9'] # Excellent - bleu blanc | |
| ], | |
| line=dict(color='#0d1117', width=2), | |
| showscale=False | |
| ), | |
| hovertemplate='<b>%{label}</b><br>Bénéfice: %{value:,.0f} XOF<br>Part: %{percentParent}<extra></extra>' | |
| )) | |
| fig_treemap.update_layout( | |
| plot_bgcolor='rgba(13, 17, 23, 0.8)', | |
| paper_bgcolor='rgba(22, 27, 34, 0.3)', | |
| font={'color': '#ffffff', 'family': 'Space Grotesk', 'size': 11}, | |
| height=400, | |
| margin=dict(t=10, b=10, l=10, r=10) | |
| ) | |
| st.plotly_chart(fig_treemap, use_container_width=True) | |
| # Légende des variations sous le treemap | |
| st.markdown("<h4 style='font-size: 0.9rem; color: #8b949e; margin-top: 24px; margin-bottom: 12px;'>Variations mensuelles</h4>", unsafe_allow_html=True) | |
| # Afficher les variations en grille 3 colonnes | |
| variations_cols = st.columns(3) | |
| for idx, row in df_treemap.iterrows(): | |
| col_index = idx % 3 | |
| with variations_cols[col_index]: | |
| variation = row['Variation'] | |
| mois_nom = row['Mois_Nom'][:3] | |
| # Couleur et symbole | |
| if variation > 0: | |
| couleur = "#54bd4b" | |
| symbole = "▲" | |
| elif variation < 0: | |
| couleur = "#f85149" | |
| symbole = "▼" | |
| else: | |
| couleur = "#8b949e" | |
| symbole = "→" | |
| st.markdown(f""" | |
| <div style=' | |
| background: rgba(22, 27, 34, 0.5); | |
| border-left: 3px solid {couleur}; | |
| padding: 10px; | |
| margin-bottom: 8px; | |
| border-radius: 4px; | |
| font-size: 0.85rem; | |
| '> | |
| <div style='display: flex; justify-content: space-between; align-items: center;'> | |
| <span style='color: #c9d1d9; font-weight: 500;'>{mois_nom}</span> | |
| <span style='color: {couleur}; font-weight: 600;'> | |
| {symbole} {abs(variation):.1f}% | |
| </span> | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Résumé | |
| st.divider() | |
| col_stat1, col_stat2, col_stat3 = st.columns(3) | |
| with col_stat1: | |
| st.metric("Total 2026", f"{total_benefices:,.0f} XOF".replace(',', ' ')) | |
| with col_stat2: | |
| mois_max = df_treemap.loc[df_treemap['Benefice'].idxmax(), 'Mois_Nom'] | |
| montant_max = df_treemap['Benefice'].max() | |
| st.metric("Meilleur mois", mois_max, f"{montant_max:,.0f} XOF".replace(',', ' ')) | |
| with col_stat3: | |
| nb_mois_actifs = len(df_treemap) | |
| st.metric("Mois actifs", f"{nb_mois_actifs}/12") | |
| else: | |
| st.info("ℹ️ Aucun bénéfice enregistré pour créer la visualisation") | |
| else: | |
| st.warning("⚠️ Colonnes 'Date_Deblocage' ou 'Cout_Credit' introuvables dans Prets_Master") | |
| # === GOTHAM SURVEILLANCE THEME CSS === | |
| 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%); | |
| } | |
| /* Application Space Grotesk UNIQUEMENT aux contenus textuels - PAS aux boutons ni icônes */ | |
| .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 discrète */ | |
| .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; | |
| } | |
| .stApp h2 { | |
| font-size: 1.3rem !important; | |
| color: #8b949e !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 - SANS Space Grotesk */ | |
| .stButton > button { | |
| background: rgba(22, 27, 34, 0.8); | |
| border: 1px solid rgba(48, 54, 61, 1); | |
| color: #c9d1d9 !important; | |
| font-weight: 500; | |
| 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); | |
| } | |
| .stButton > button:hover { | |
| background: rgba(33, 38, 45, 1); | |
| border-color: rgba(88, 166, 255, 0.4); | |
| box-shadow: 0 0 8px rgba(88, 166, 255, 0.15); | |
| } | |
| /* 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; | |
| } | |
| /* Info boxes */ | |
| .stAlert { | |
| background: rgba(22, 27, 34, 0.6); | |
| border: 1px solid rgba(48, 54, 61, 0.8); | |
| border-left: 3px solid rgba(88, 166, 255, 0.6); | |
| border-radius: 4px; | |
| color: #8b949e; | |
| font-size: 0.9rem; | |
| font-family: 'Space Grotesk', sans-serif !important; | |
| } | |
| /* Checkbox */ | |
| .stCheckbox label { | |
| color: #8b949e !important; | |
| font-weight: 500; | |
| font-size: 0.85rem; | |
| font-family: 'Space Grotesk', sans-serif !important; | |
| } | |
| /* Divider */ | |
| hr { | |
| border: none; | |
| height: 1px; | |
| background: rgba(48, 54, 61, 0.6); | |
| margin: 2rem 0; | |
| } | |
| /* Download button - SANS Space Grotesk */ | |
| .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.15); | |
| border-color: rgba(88, 166, 255, 0.6); | |
| } | |
| /* 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); | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # === APPEL DU MODULE ML FEATURE STORE === | |
| from Analytics.ML_Feature_Store_Analytics import show_ml_feature_store | |
| show_ml_feature_store(client, sheet_name) |