Spaces:
Running
Running
Update src/modules/ml_dashboard.py
Browse files- src/modules/ml_dashboard.py +159 -29
src/modules/ml_dashboard.py
CHANGED
|
@@ -5,35 +5,165 @@ import plotly.graph_objects as go
|
|
| 5 |
from datetime import datetime, timedelta
|
| 6 |
import numpy as np
|
| 7 |
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
else:
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
# === GOTHAM SURVEILLANCE THEME CSS ===
|
| 39 |
st.markdown("""
|
|
@@ -430,7 +560,7 @@ def show_ml_features(client, sheet_name):
|
|
| 430 |
|
| 431 |
# 6. FEATURES TEMPORELLES
|
| 432 |
with st.expander("Features Temporelles", expanded=True):
|
| 433 |
-
df_full['Date_Creation'] = pd.to_datetime(df_full
|
| 434 |
df_full['Anciennete_Client_Jours'] = (datetime.now() - df_full['Date_Creation']).dt.days
|
| 435 |
df_full['Anciennete_Client_Mois'] = (df_full['Anciennete_Client_Jours'] / 30.44).round(1)
|
| 436 |
|
|
|
|
| 5 |
from datetime import datetime, timedelta
|
| 6 |
import numpy as np
|
| 7 |
|
| 8 |
+
|
| 9 |
+
# Fonction helper pour charger les données
|
| 10 |
+
def get_data_from_sheet(sheet_name_tab):
|
| 11 |
+
try:
|
| 12 |
+
sh = client.open(sheet_name)
|
| 13 |
+
ws = sh.worksheet(sheet_name_tab)
|
| 14 |
+
return pd.DataFrame(ws.get_all_records())
|
| 15 |
+
except:
|
| 16 |
+
return pd.DataFrame()
|
| 17 |
+
|
| 18 |
+
# Chargement des données nécessaires
|
| 19 |
+
df_prets_master = get_data_from_sheet("Prets_Master")
|
| 20 |
+
df_prets_update = get_data_from_sheet("Prets_Update")
|
| 21 |
+
df_capital_invest = get_data_from_sheet("Capital_Investissement")
|
| 22 |
+
df_clients = get_data_from_sheet("Clients_KYC")
|
| 23 |
+
|
| 24 |
+
# === En-tête ===
|
| 25 |
+
st.header("OBJECT EXPLORER")
|
| 26 |
+
st.success(f"✅ Liaison Directe établie avec '{sheet_name}'")
|
| 27 |
+
|
| 28 |
+
# === CALCULS POUR LES MÉTRIQUES ===
|
| 29 |
+
|
| 30 |
+
# 1. Capital de l'entreprise (somme de Capital_Investissement)
|
| 31 |
+
capital_entreprise = 0
|
| 32 |
+
capital_variation = 0
|
| 33 |
+
if not df_capital_invest.empty and 'Capital' in df_capital_invest.columns:
|
| 34 |
+
df_capital_invest['Capital_Num'] = pd.to_numeric(
|
| 35 |
+
df_capital_invest['Capital'].astype(str).str.replace('FCFA', '').str.replace(' ', '').str.strip(),
|
| 36 |
+
errors='coerce'
|
| 37 |
+
).fillna(0)
|
| 38 |
+
capital_entreprise = df_capital_invest['Capital_Num'].sum()
|
| 39 |
+
# Variation = dernier vs précédent
|
| 40 |
+
if len(df_capital_invest) > 1:
|
| 41 |
+
derniere_entree = df_capital_invest.iloc[-1]['Capital_Num']
|
| 42 |
+
avant_derniere = df_capital_invest.iloc[-2]['Capital_Num']
|
| 43 |
+
if avant_derniere > 0:
|
| 44 |
+
capital_variation = ((derniere_entree - avant_derniere) / avant_derniere) * 100
|
| 45 |
+
|
| 46 |
+
# 2. Capital dehors (prêts non remboursés)
|
| 47 |
+
capital_dehors = 0
|
| 48 |
+
if not df_prets_master.empty and 'Montant_Capital' in df_prets_master.columns:
|
| 49 |
+
df_prets_master['Montant_Capital_Num'] = pd.to_numeric(df_prets_master['Montant_Capital'], errors='coerce').fillna(0)
|
| 50 |
+
capital_dehors = df_prets_master[df_prets_master['Statut'] != 'REMBOURSE']['Montant_Capital_Num'].sum()
|
| 51 |
+
|
| 52 |
+
# 3. Flux attendu (Montant_Total des prêts non remboursés)
|
| 53 |
+
flux_attendu = 0
|
| 54 |
+
flux_variation = 0
|
| 55 |
+
if not df_prets_master.empty and 'Montant_Total' in df_prets_master.columns:
|
| 56 |
+
df_prets_master['Montant_Total_Num'] = pd.to_numeric(df_prets_master['Montant_Total'], errors='coerce').fillna(0)
|
| 57 |
+
flux_attendu = df_prets_master[df_prets_master['Statut'] != 'REMBOURSE']['Montant_Total_Num'].sum()
|
| 58 |
+
# Variation = différence avec capital dehors
|
| 59 |
+
if capital_dehors > 0:
|
| 60 |
+
flux_variation = ((flux_attendu - capital_dehors) / capital_dehors) * 100
|
| 61 |
+
|
| 62 |
+
# 4. Score de liquidité
|
| 63 |
+
score_liquidite = 0
|
| 64 |
+
score_tendance = "Stable"
|
| 65 |
+
if capital_entreprise > 0:
|
| 66 |
+
score_liquidite = (capital_dehors / capital_entreprise) * 10
|
| 67 |
+
score_liquidite = min(score_liquidite, 10)
|
| 68 |
+
# Tendance basée sur le ratio
|
| 69 |
+
ratio = (capital_dehors / capital_entreprise) * 100
|
| 70 |
+
if ratio > 70:
|
| 71 |
+
score_tendance = "▲ Élevé"
|
| 72 |
+
elif ratio > 40:
|
| 73 |
+
score_tendance = "→ Moyen"
|
| 74 |
else:
|
| 75 |
+
score_tendance = "▼ Faible"
|
| 76 |
+
|
| 77 |
+
# 5. Bénéfices nets 2026
|
| 78 |
+
benefices_nets = 0
|
| 79 |
+
benefices_variation = 0
|
| 80 |
+
if not df_prets_master.empty and 'Cout_Credit' in df_prets_master.columns:
|
| 81 |
+
df_prets_master['Cout_Credit_Num'] = pd.to_numeric(df_prets_master['Cout_Credit'], errors='coerce').fillna(0)
|
| 82 |
+
benefices_nets = df_prets_master['Cout_Credit_Num'].sum()
|
| 83 |
+
# Simulation variation (peut être calculée vs mois précédent si date disponible)
|
| 84 |
+
benefices_variation = 5.2 # Placeholder - à calculer avec historique
|
| 85 |
+
|
| 86 |
+
# 6. Objectif 2026
|
| 87 |
+
objectif_2026 = 2_200_000
|
| 88 |
+
progression_objectif = (benefices_nets / objectif_2026) * 100 if objectif_2026 > 0 else 0
|
| 89 |
+
|
| 90 |
+
# 7. Reste à générer
|
| 91 |
+
reste_a_generer = max(0, objectif_2026 - benefices_nets)
|
| 92 |
+
reste_variation = -((reste_a_generer / objectif_2026) * 100) if objectif_2026 > 0 else 0
|
| 93 |
+
|
| 94 |
+
# 8. Reste à payer
|
| 95 |
+
reste_a_payer = 0
|
| 96 |
+
nb_prets_actifs = 0
|
| 97 |
+
if not df_prets_master.empty and 'Montant_Total' in df_prets_master.columns:
|
| 98 |
+
prets_actifs_master = df_prets_master[df_prets_master['Statut'] != 'REMBOURSE']
|
| 99 |
+
nb_prets_actifs = len(prets_actifs_master)
|
| 100 |
+
reste_a_payer = prets_actifs_master['Montant_Total_Num'].sum()
|
| 101 |
+
|
| 102 |
+
# Ajouter prets_update (sans doublon)
|
| 103 |
+
if not df_prets_update.empty and 'ID_Pret' in df_prets_update.columns:
|
| 104 |
+
df_prets_update['Montant_Total_Num'] = pd.to_numeric(df_prets_update['Montant_Total'], errors='coerce').fillna(0)
|
| 105 |
+
prets_update_uniques = df_prets_update[
|
| 106 |
+
(~df_prets_update['ID_Pret'].isin(prets_actifs_master['ID_Pret'].tolist())) &
|
| 107 |
+
(df_prets_update['Statut'] != 'REMBOURSE')
|
| 108 |
+
]
|
| 109 |
+
reste_a_payer += prets_update_uniques['Montant_Total_Num'].sum()
|
| 110 |
+
nb_prets_actifs += len(prets_update_uniques)
|
| 111 |
+
|
| 112 |
+
# === AFFICHAGE DES MÉTRIQUES (2 lignes de 4) ===
|
| 113 |
+
|
| 114 |
+
# Ligne 1
|
| 115 |
+
col1, col2, col3, col4 = st.columns(4)
|
| 116 |
+
|
| 117 |
+
col1.metric(
|
| 118 |
+
"CAPITAL D'INVESTISSEMENT",
|
| 119 |
+
f"{capital_entreprise:,.0f} XOF".replace(',', ' '),
|
| 120 |
+
delta=f"{'▲' if capital_variation > 0 else '▼'} {abs(capital_variation):.1f}%" if capital_variation != 0 else "Stable"
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
col2.metric(
|
| 124 |
+
"CAPITAL DEHORS",
|
| 125 |
+
f"{capital_dehors:,.0f} XOF".replace(',', ' '),
|
| 126 |
+
delta=f"{(capital_dehors/capital_entreprise*100 if capital_entreprise > 0 else 0):.1f}% du capital investi"
|
| 127 |
+
)
|
| 128 |
+
|
| 129 |
+
col3.metric(
|
| 130 |
+
"FLUX ATTENDU",
|
| 131 |
+
f"{flux_attendu:,.0f} XOF".replace(',', ' '),
|
| 132 |
+
delta=f"{'▲' if flux_variation > 0 else '▼'} {abs(flux_variation):.1f}% vs capital" if flux_variation != 0 else "Stable"
|
| 133 |
+
)
|
| 134 |
+
|
| 135 |
+
col4.metric(
|
| 136 |
+
"SCORE LIQUIDITÉ",
|
| 137 |
+
f"{score_liquidite:.1f}/10",
|
| 138 |
+
delta=score_tendance
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
# Ligne 2
|
| 142 |
+
col5, col6, col7, col8 = st.columns(4)
|
| 143 |
+
|
| 144 |
+
col5.metric(
|
| 145 |
+
"BÉNÉFICES NETS 2026",
|
| 146 |
+
f"{benefices_nets:,.0f} XOF".replace(',', ' '),
|
| 147 |
+
delta=f"▲ {benefices_variation:.1f}%" if benefices_variation > 0 else "Stable"
|
| 148 |
+
)
|
| 149 |
+
|
| 150 |
+
col6.metric(
|
| 151 |
+
"OBJECTIF 2026",
|
| 152 |
+
f"{objectif_2026:,.0f} XOF".replace(',', ' '),
|
| 153 |
+
delta=f"{progression_objectif:.1f}% atteint"
|
| 154 |
+
)
|
| 155 |
+
|
| 156 |
+
col7.metric(
|
| 157 |
+
"RESTE À GÉNÉRER",
|
| 158 |
+
f"{reste_a_generer:,.0f} XOF".replace(',', ' '),
|
| 159 |
+
delta=f"▼ {abs(reste_variation):.1f}%" if reste_a_generer > 0 else "✓ Objectif atteint !"
|
| 160 |
+
)
|
| 161 |
+
|
| 162 |
+
col8.metric(
|
| 163 |
+
"RESTE À PAYER",
|
| 164 |
+
f"{reste_a_payer:,.0f} XOF".replace(',', ' '),
|
| 165 |
+
delta=f"{nb_prets_actifs} prêt{'s' if nb_prets_actifs > 1 else ''} actif{'s' if nb_prets_actifs > 1 else ''}"
|
| 166 |
+
)
|
| 167 |
|
| 168 |
# === GOTHAM SURVEILLANCE THEME CSS ===
|
| 169 |
st.markdown("""
|
|
|
|
| 560 |
|
| 561 |
# 6. FEATURES TEMPORELLES
|
| 562 |
with st.expander("Features Temporelles", expanded=True):
|
| 563 |
+
df_full['Date_Creation'] = pd.to_datetime(df_full.get('Date_Creation', pd.Series()), errors='coerce')
|
| 564 |
df_full['Anciennete_Client_Jours'] = (datetime.now() - df_full['Date_Creation']).dt.days
|
| 565 |
df_full['Anciennete_Client_Mois'] = (df_full['Anciennete_Client_Jours'] / 30.44).round(1)
|
| 566 |
|