import streamlit as st
import pandas as pd
import pydeck as pdk
from geopy.geocoders import Nominatim
from geopy.exc import GeocoderTimedOut
import time
import requests
import datetime
# --- CONSTANTES ---
COLOR_BLUE_GOTHAM = "#25C1F7"
COLOR_GREEN_ZONE = "#54BD4B"
COLOR_YELLOW_LINE = "#FFD700"
# === CSS SPÉCIFIQUE MAP DASHBOARD (Styles additionnels seulement) ===
def apply_map_dashboard_styles():
st.markdown("""
""", unsafe_allow_html=True)
# --- FONCTION GÉOCODAGE ---
@st.cache_data(show_spinner=False, ttl=3600)
def geocode_addresses(df_clients):
"""Géocodage via LocationIQ"""
if 'Adresse' not in df_clients.columns or 'ID_Client' not in df_clients.columns:
return pd.DataFrame()
LOCATIONIQ_TOKEN = "pk.e1561a89e1ed2bc2ddeb3ee53fd88fb8"
geocoded_data = []
for index, row in df_clients.iterrows():
address = str(row['Adresse']).strip()
client_id = row['ID_Client']
try:
url = "https://us1.locationiq.com/v1/search.php"
params = {
'key': LOCATIONIQ_TOKEN,
'q': address,
'format': 'json',
'countrycodes': 'sn',
'limit': 1,
'accept-language': 'fr'
}
response = requests.get(url, params=params, timeout=10)
if response.status_code == 200:
data = response.json()
if data and len(data) > 0:
result = data[0]
lat = float(result['lat'])
lon = float(result['lon'])
geocoded_data.append({
"ID": client_id,
"Nom": row.get('Nom_Complet', 'Client Inconnu'),
"Adresse": address,
"lat": lat,
"lon": lon,
"Revenus": row.get('Revenus_Mensuels', 0)
})
time.sleep(1.2)
except Exception as e:
pass
return pd.DataFrame(geocoded_data)
# --- FONCTION PRINCIPALE ---
def show_map_dashboard(client, sheet_name):
# Appliquer les styles spécifiques
apply_map_dashboard_styles()
# Wrapper pour isolation des styles
st.markdown('
', unsafe_allow_html=True)
st.markdown("
GLOBAL CONTROL TOWER
", unsafe_allow_html=True)
# === CHARGEMENT DES DONNÉES ===
try:
sh = client.open(sheet_name)
ws_clients = sh.worksheet("Clients_KYC")
ws_prets = sh.worksheet("Prets_Master")
df_raw = pd.DataFrame(ws_clients.get_all_records())
df_prets = pd.DataFrame(ws_prets.get_all_records())
except Exception as e:
st.error(f"Erreur de lecture : {e}")
st.markdown('', unsafe_allow_html=True)
return
if df_raw.empty:
st.info("Aucune donnée client.")
st.markdown('', unsafe_allow_html=True)
return
# === 5 METRICS TEMPS RÉEL ===
col_m1, col_m2, col_m3, col_m4, col_m5 = st.columns(5)
# Calculs depuis Prets_Master
if not df_prets.empty:
total_prets = len(df_prets)
# Conversion sécurisée du montant en numérique
if 'Montant_Capital' in df_prets.columns:
montant_total = pd.to_numeric(df_prets['Montant_Capital'], errors='coerce').fillna(0).sum()
else:
montant_total = 0
prets_actifs = len(df_prets[df_prets['Statut'] == 'Actif']) if 'Statut' in df_prets.columns else 0
# Conversion sécurisée du taux en numérique
if 'Taux_Hebdo' in df_prets.columns:
taux_moyen = pd.to_numeric(df_prets['Taux_Hebdo'], errors='coerce').fillna(0).mean()
else:
taux_moyen = 0
prets_semaine = len(df_prets.tail(5)) if len(df_prets) >= 5 else len(df_prets)
else:
total_prets = montant_total = prets_actifs = taux_moyen = prets_semaine = 0
with col_m1:
st.metric("PRÊTS ACTIFS", f"{prets_actifs}", f"+{prets_semaine} (7j)")
with col_m2:
st.metric("CAPITAL DÉPLOYÉ", f"{montant_total/1e6:.1f}M XOF", "+12%")
with col_m3:
st.metric("TAUX MOYEN", f"{taux_moyen:.1f}%", "-0.2%")
with col_m4:
st.metric("CLIENTS ACTIFS", f"{len(df_raw)}", "+3")
with col_m5:
st.metric("RECOUVREMENT", "94.2%", "+1.8%")
st.markdown("---")
# === GÉOCODAGE ===
with st.spinner(" Triangulation satellites..."):
df_map = geocode_addresses(df_raw)
if df_map.empty:
st.warning("⚠️ Impossible de géolocaliser les adresses.")
st.markdown('', unsafe_allow_html=True)
return
# === LAYOUT PRINCIPAL : 3 COLONNES ===
col_left, col_center, col_right = st.columns([1, 3, 1])
# === COLONNE GAUCHE : CLIENTS ===
with col_left:
st.markdown("### TARGETS")
st.caption(f"{len(df_map)} clients géolocalisés")
# Session state pour focus
if 'focus_lat' not in st.session_state:
st.session_state['focus_lat'] = df_map['lat'].mean()
st.session_state['focus_lon'] = df_map['lon'].mean()
st.session_state['focus_id'] = None
for idx, row in df_map.iterrows():
client_key = f"client_{row['ID']}"
with st.expander(f"🔹 {row['ID']}", expanded=False):
st.caption(f"{row['Nom']}")
st.caption(f" {row['Adresse'][:35]}...")
st.caption(f" {row['Revenus']:,.0f} XOF")
if st.button("LOCALISER", key=f"loc_{client_key}", use_container_width=True):
st.session_state['focus_lat'] = row['lat']
st.session_state['focus_lon'] = row['lon']
st.session_state['focus_id'] = row['ID']
st.rerun()
# === COLONNE CENTRALE : CARTE ===
with col_center:
st.markdown("### TACTICAL MAP")
# Préparer données clients
clients_js = []
for _, row in df_map.iterrows():
clients_js.append({
'lat': row['lat'],
'lng': row['lon'],
'id': row['ID'],
'nom': row['Nom'],
'adresse': row['Adresse']
})
# Focus
focus_lat = st.session_state.get('focus_lat', df_map['lat'].mean())
focus_lng = st.session_state.get('focus_lon', df_map['lon'].mean())
# HTML Mapbox
mapbox_html = f"""
"""
st.components.v1.html(mapbox_html, height=500)
# === COLONNE DROITE : ALERTES ===
with col_right:
st.markdown("### LIVE FEED")
now = datetime.datetime.now()
alerts = [
{
'type': 'critical',
'title': 'SEUIL ATTEINT',
'message': 'NPL > 15% détecté',
'time': (now - datetime.timedelta(minutes=2)).strftime('%H:%M')
},
{
'type': 'warning',
'title': 'ANOMALIE DÉTECTÉE',
'message': 'CA mensuel -12%',
'time': (now - datetime.timedelta(minutes=15)).strftime('%H:%M')
},
{
'type': 'success',
'title': 'VALIDATION',
'message': f'{df_map.iloc[0]["ID"]} approuvé',
'time': (now - datetime.timedelta(minutes=23)).strftime('%H:%M')
},
{
'type': 'info',
'title': 'RECOMMANDATION',
'message': 'Augmenter taux +0.5%',
'time': (now - datetime.timedelta(hours=1)).strftime('%H:%M')
},
{
'type': 'warning',
'title': 'RETARD PAIEMENT',
'message': '3 clients en J+7',
'time': (now - datetime.timedelta(hours=2)).strftime('%H:%M')
},
]
for alert in alerts:
st.markdown(f"""
{alert['title']}
{alert['time']}
{alert['message']}
""", unsafe_allow_html=True)
# === STATS ANSD ===
st.markdown("---")
st.markdown("### INDICATEURS MACROÉCONOMIQUES ANSD")
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Croissance PIB", "5.2%", "+0.3%")
st.metric("Taux BCEAO", "3.0%", "-0.5%")
st.metric("NPL Bancaires", "14.8%", "+1.2%")
with col2:
st.metric("Emploi Informel", "68%", "-2%")
st.metric("Dette Publique", "72% PIB", "+3%")
st.metric("Transferts Diaspora", "2.1B USD", "+5%")
with col3:
st.metric("Inflation Alimentaire", "8.3%", "+1.5%")
st.metric("Taux EUR/USD", "1.08", "-0.02")
st.metric("Production Agricole", "92 idx", "-8 idx")
# Fermeture du wrapper
st.markdown('', unsafe_allow_html=True)