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='%{x}
Bénéfice: %{y:,.0f} XOF' )) # 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='%{x}
Capital Sorti: %{y:,.0f} XOF' )) # 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("

Répartition mensuelle des bénéfices

", 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']}
{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='%{label}
Bénéfice: %{value:,.0f} XOF
Part: %{percentParent}' )) 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("

Variations mensuelles

", 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"""
{mois_nom} {symbole} {abs(variation):.1f}%
""", 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(""" """, 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)