Spaces:
Running
Running
| import streamlit as st | |
| import pandas as pd | |
| import sys | |
| import os | |
| import plotly.graph_objects as go | |
| from plotly.subplots import make_subplots | |
| import numpy as np | |
| # Import du module d'analyse | |
| sys.path.append(os.path.join(os.path.dirname(__file__), '..')) | |
| from Analytics.VortexOutFlux import VortexOutFlux | |
| from Analytics.DataSyncForecasting import actualiser_forecasting_depuis_prets | |
| # === CSS AMÉLIORÉ - STYLE GOTHAM PROFESSIONNEL === | |
| def apply_forecasting_styles(): | |
| st.markdown(""" | |
| <style> | |
| /* === STYLES SPÉCIFIQUES MODULE FORECASTING === */ | |
| /* Wrapper pour isolation */ | |
| #forecasting-module { | |
| font-family: 'Space Grotesk', sans-serif; | |
| } | |
| /* Headers spécifiques */ | |
| #forecasting-module h1 { | |
| font-size: 1.8rem !important; | |
| margin-bottom: 24px !important; | |
| color: #58a6ff !important; | |
| border-bottom: 2px solid rgba(88, 166, 255, 0.3); | |
| padding-bottom: 12px; | |
| } | |
| #forecasting-module h2 { | |
| font-size: 1.4rem !important; | |
| margin-bottom: 16px !important; | |
| color: #8b949e !important; | |
| } | |
| #forecasting-module h3 { | |
| font-size: 1.1rem !important; | |
| margin-bottom: 12px !important; | |
| color: #58a6ff !important; | |
| } | |
| /* Metrics cards pour KPIs */ | |
| #forecasting-module [data-testid="stMetric"] { | |
| background: rgba(22, 27, 34, 0.7); | |
| border: 1px solid rgba(88, 166, 255, 0.4); | |
| padding: 16px !important; | |
| border-radius: 6px; | |
| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); | |
| } | |
| #forecasting-module [data-testid="stMetric"] label { | |
| color: #8b949e !important; | |
| font-size: 0.75rem !important; | |
| font-weight: 600 !important; | |
| text-transform: uppercase; | |
| letter-spacing: 0.8px; | |
| } | |
| #forecasting-module [data-testid="stMetric"] [data-testid="stMetricValue"] { | |
| color: #58a6ff !important; | |
| font-size: 1.6rem !important; | |
| font-weight: 700 !important; | |
| } | |
| #forecasting-module [data-testid="stMetric"] [data-testid="stMetricDelta"] { | |
| color: #54bd4b !important; | |
| font-size: 0.9rem !important; | |
| } | |
| /* Carte de prédiction style Palantir - MINIMALISTE */ | |
| .forecast-card { | |
| background: rgba(22, 27, 34, 0.4); | |
| border: 1px solid rgba(88, 166, 255, 0.2); | |
| border-radius: 4px; | |
| padding: 16px; | |
| margin: 12px 0; | |
| transition: border-color 0.2s ease; | |
| } | |
| .forecast-card:hover { | |
| border-color: rgba(88, 166, 255, 0.4); | |
| } | |
| .forecast-card::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| height: 2px; | |
| background: linear-gradient(90deg, rgba(88, 166, 255, 0.8), rgba(88, 166, 255, 0.3)); | |
| } | |
| .forecast-card-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 16px; | |
| padding-bottom: 12px; | |
| border-bottom: 1px solid rgba(88, 166, 255, 0.2); | |
| } | |
| .forecast-card-title { | |
| font-size: 1.2rem; | |
| font-weight: 700; | |
| color: #c9d1d9; | |
| margin: 0; | |
| } | |
| .forecast-card-badge { | |
| background: rgba(88, 166, 255, 0.15); | |
| color: #58a6ff; | |
| padding: 4px 12px; | |
| border-radius: 12px; | |
| font-size: 0.75rem; | |
| font-weight: 600; | |
| text-transform: uppercase; | |
| border: 1px solid rgba(88, 166, 255, 0.3); | |
| } | |
| .forecast-card-badge.positive { | |
| background: rgba(88, 166, 255, 0.15); | |
| color: #58a6ff; | |
| border-color: rgba(88, 166, 255, 0.3); | |
| } | |
| .forecast-card-badge.negative { | |
| background: rgba(139, 148, 158, 0.15); | |
| color: #8b949e; | |
| border-color: rgba(139, 148, 158, 0.3); | |
| } | |
| .forecast-card-body { | |
| color: #c9d1d9; | |
| } | |
| .forecast-card-row { | |
| display: flex; | |
| justify-content: space-between; | |
| padding: 10px 0; | |
| border-bottom: 1px solid rgba(48, 54, 61, 0.3); | |
| } | |
| .forecast-card-row:last-child { | |
| border-bottom: none; | |
| } | |
| .forecast-card-label { | |
| color: #8b949e; | |
| font-size: 0.9rem; | |
| font-weight: 500; | |
| } | |
| .forecast-card-value { | |
| color: #c9d1d9; | |
| font-weight: 600; | |
| font-size: 1rem; | |
| } | |
| .forecast-card-value.highlight { | |
| color: #58a6ff; | |
| font-size: 1.2rem; | |
| font-weight: 700; | |
| } | |
| /* Bloc d'interprétation */ | |
| .interpretation-box { | |
| background: rgba(88, 166, 255, 0.05); | |
| border-left: 4px solid #58a6ff; | |
| border-radius: 4px; | |
| padding: 16px; | |
| margin: 16px 0; | |
| } | |
| .interpretation-box h4 { | |
| color: #58a6ff !important; | |
| margin: 0 0 12px 0 !important; | |
| font-size: 1rem !important; | |
| } | |
| .interpretation-box p { | |
| color: #c9d1d9; | |
| margin: 8px 0; | |
| line-height: 1.6; | |
| font-size: 0.9rem; | |
| } | |
| /* Bloc métriques de performance */ | |
| .metrics-performance { | |
| background: rgba(22, 27, 34, 0.6); | |
| border: 1px solid rgba(48, 54, 61, 0.8); | |
| border-radius: 6px; | |
| padding: 16px; | |
| margin: 16px 0; | |
| } | |
| .metrics-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | |
| gap: 16px; | |
| margin-top: 12px; | |
| } | |
| .metric-item { | |
| background: rgba(13, 17, 23, 0.8); | |
| padding: 12px; | |
| border-radius: 4px; | |
| border-left: 3px solid #58a6ff; | |
| } | |
| .metric-item-label { | |
| color: #8b949e; | |
| font-size: 0.75rem; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| margin-bottom: 4px; | |
| } | |
| .metric-item-value { | |
| color: #58a6ff; | |
| font-size: 1.3rem; | |
| font-weight: 700; | |
| } | |
| /* Bouton de synchronisation - STYLE GOTHAM MONOCHROME */ | |
| [data-testid="column"] .stButton[data-baseweb="button"] > button[kind="primary"] { | |
| background: rgba(88, 166, 255, 0.15) !important; | |
| border: 2px solid #58a6ff !important; | |
| color: #58a6ff !important; | |
| font-weight: 700 !important; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| } | |
| [data-testid="column"] .stButton[data-baseweb="button"] > button[kind="primary"]:hover { | |
| background: rgba(88, 166, 255, 0.25) !important; | |
| border-color: #79b8ff !important; | |
| box-shadow: 0 0 20px rgba(88, 166, 255, 0.3) !important; | |
| } | |
| /* Divider */ | |
| #forecasting-module hr { | |
| background: rgba(88, 166, 255, 0.3); | |
| height: 2px; | |
| margin: 24px 0; | |
| } | |
| /* Alert boxes */ | |
| #forecasting-module .stAlert { | |
| border-radius: 6px; | |
| padding: 12px 16px !important; | |
| } | |
| #forecasting-module .stAlert[kind="info"] { | |
| background: rgba(88, 166, 255, 0.1) !important; | |
| border-left: 4px solid #58a6ff !important; | |
| } | |
| #forecasting-module .stAlert[kind="warning"] { | |
| background: rgba(243, 156, 18, 0.1) !important; | |
| border-left: 4px solid #f39c12 !important; | |
| } | |
| #forecasting-module .stAlert[kind="success"] { | |
| background: rgba(84, 189, 75, 0.1) !important; | |
| border-left: 4px solid #54bd4b !important; | |
| } | |
| /* Expanders */ | |
| #forecasting-module .streamlit-expanderHeader { | |
| background: rgba(22, 27, 34, 0.7) !important; | |
| border-left: 3px solid rgba(88, 166, 255, 0.6) !important; | |
| padding: 12px 16px !important; | |
| font-weight: 600 !important; | |
| color: #8b949e !important; | |
| } | |
| #forecasting-module .streamlit-expanderHeader:hover { | |
| background: rgba(33, 38, 45, 0.9) !important; | |
| border-left-color: rgba(88, 166, 255, 0.9) !important; | |
| } | |
| #forecasting-module .streamlit-expanderContent { | |
| background: rgba(13, 17, 23, 0.7); | |
| border: 1px solid rgba(48, 54, 61, 0.6); | |
| padding: 16px !important; | |
| } | |
| /* Animation de chargement */ | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0.5; } | |
| } | |
| .loading-indicator { | |
| animation: pulse 2s ease-in-out infinite; | |
| color: #58a6ff; | |
| text-align: center; | |
| padding: 20px; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| def render_forecast_card(month_data): | |
| """Génère une carte de prédiction mensuelle style Gotham""" | |
| date_str = month_data['Date'].strftime("%B %Y") | |
| montant = month_data['Montant_Predit'] | |
| lower = month_data['Borne_Inf'] | |
| upper = month_data['Borne_Sup'] | |
| # Calcul de la marge d'erreur | |
| margin = ((upper - lower) / 2) / montant * 100 if montant > 0 else 0 | |
| html = f""" | |
| <div class="forecast-card"> | |
| <div class="forecast-card-header"> | |
| <h4 class="forecast-card-title">{date_str}</h4> | |
| <span class="forecast-card-badge">Prédiction</span> | |
| </div> | |
| <div class="forecast-card-body"> | |
| <div class="forecast-card-row"> | |
| <span class="forecast-card-label">Montant Prédit</span> | |
| <span class="forecast-card-value highlight">{montant:,.0f} XOF</span> | |
| </div> | |
| <div class="forecast-card-row"> | |
| <span class="forecast-card-label">Intervalle de Confiance (95%)</span> | |
| <span class="forecast-card-value">{lower:,.0f} - {upper:,.0f} XOF</span> | |
| </div> | |
| <div class="forecast-card-row"> | |
| <span class="forecast-card-label">Marge d'Erreur</span> | |
| <span class="forecast-card-value">± {margin:.1f}%</span> | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| return html | |
| def show_forecasting_module(client, sheet_name): | |
| """Module principal de prévision des flux de sortie""" | |
| # Appliquer les styles | |
| apply_forecasting_styles() | |
| # Wrapper pour isolation | |
| st.markdown('<div id="forecasting-module">', unsafe_allow_html=True) | |
| st.header("JASMINE - PRÉDICTION DES FLUX DE SORTIE") | |
| st.caption("Analyse prédictive en temps réel basée sur la régression linéaire") | |
| # === SECTION SYNCHRONISATION === | |
| st.divider() | |
| col_sync1, col_sync2 = st.columns([3, 1]) | |
| with col_sync1: | |
| st.markdown(""" | |
| <div style="background: rgba(88, 166, 255, 0.05); border-left: 4px solid #58a6ff; padding: 12px; border-radius: 4px;"> | |
| <p style="color: #8b949e; margin: 0; font-size: 0.9rem;"> | |
| <strong style="color: #58a6ff;"> Synchronisation automatique :</strong> | |
| Les données de flux de sortie sont calculées automatiquement depuis les déblocages de prêts (Prets_Master). | |
| Cliquez sur le bouton pour actualiser. | |
| </p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| with col_sync2: | |
| if st.button(" Synchroniser", use_container_width=True, type="primary"): | |
| with st.spinner("Synchronisation en cours..."): | |
| result = actualiser_forecasting_depuis_prets(client, sheet_name) | |
| if result['success']: | |
| stats = result['stats'] | |
| st.success(f"✅ {result['message']}") | |
| # Afficher les détails | |
| with st.expander(" Détails de la synchronisation", expanded=True): | |
| col1, col2, col3 = st.columns(3) | |
| with col1: | |
| st.metric("Mois Conservés", stats['nb_conserves'], | |
| help="Mois gardés de l'historique manuel") | |
| with col2: | |
| st.metric("Mois Mis à Jour", stats['nb_mis_a_jour'], | |
| help="Mois recalculés depuis Prets_Master") | |
| with col3: | |
| st.metric("Mois Ajoutés", stats['nb_ajoutes'], | |
| help="Nouveaux mois détectés") | |
| if stats['nb_mis_a_jour'] > 0: | |
| st.info(f"**Mois mis à jour :** {', '.join(stats['mois_mis_a_jour'])}") | |
| if stats['nb_ajoutes'] > 0: | |
| st.success(f"**Nouveaux mois ajoutés :** {', '.join(stats['mois_ajoutes'])}") | |
| # Forcer le rechargement | |
| st.rerun() | |
| else: | |
| st.error(f"❌ {result['message']}") | |
| try: | |
| # Connexion à Google Sheets | |
| sh = client.open(sheet_name) | |
| ws_forecasting = sh.worksheet("Forecasting") | |
| # Chargement des données | |
| df_forecasting = pd.DataFrame(ws_forecasting.get_all_records()) | |
| if df_forecasting.empty or len(df_forecasting) < 2: | |
| st.warning("⚠️ Données insuffisantes pour effectuer une prédiction. Minimum 2 points de données requis.") | |
| st.markdown('</div>', unsafe_allow_html=True) | |
| return | |
| # Vérification des colonnes | |
| if 'Date' not in df_forecasting.columns or 'Montant_Total_Sortie' not in df_forecasting.columns: | |
| st.error("❌ Structure de données invalide. Colonnes requises : 'Date', 'Montant_Total_Sortie'") | |
| st.markdown('</div>', unsafe_allow_html=True) | |
| return | |
| # Initialisation de l'analyseur | |
| analyzer = VortexOutFlux(df_forecasting) | |
| # Section 1 : Vue d'ensemble des données | |
| st.divider() | |
| st.subheader(" Vue d'Ensemble des Données Historiques") | |
| col1, col2, col3, col4 = st.columns(4) | |
| with col1: | |
| nb_points = len(analyzer.df) | |
| st.metric("Points de Données", nb_points) | |
| with col2: | |
| flux_moyen = analyzer.df['Montant_Total_Sortie'].mean() | |
| st.metric("Flux Moyen", f"{flux_moyen:,.0f} XOF") | |
| with col3: | |
| flux_total = analyzer.df['Montant_Total_Sortie'].sum() | |
| st.metric("Flux Total", f"{flux_total:,.0f} XOF") | |
| with col4: | |
| date_debut = analyzer.df['Date'].min().strftime("%m/%Y") | |
| date_fin = analyzer.df['Date'].max().strftime("%m/%Y") | |
| st.metric("Période", f"{date_debut} - {date_fin}") | |
| # Section 2 : Prédictions | |
| st.divider() | |
| st.subheader(" Prédictions pour les 3 Prochains Mois") | |
| with st.spinner("Calcul des prédictions en cours..."): | |
| predictions_result = analyzer.predict_next_months(n_months=3) | |
| if 'error' in predictions_result: | |
| st.error(f"❌ {predictions_result['error']}") | |
| st.markdown('</div>', unsafe_allow_html=True) | |
| return | |
| predictions_df = predictions_result['predictions'] | |
| models = predictions_result['models'] | |
| # Affichage des cartes de prédiction | |
| cols = st.columns(3) | |
| for idx, (_, row) in enumerate(predictions_df.iterrows()): | |
| with cols[idx]: | |
| st.markdown(render_forecast_card(row), unsafe_allow_html=True) | |
| # Section 3 : Analyse de Tendance et Interprétation | |
| st.divider() | |
| st.subheader(" Analyse de Tendance") | |
| interpretations = analyzer.get_interpretation(models) | |
| # Affichage de la tendance principale | |
| tendance_badge_class = "positive" if interpretations['tendance'] == "CROISSANCE" else "negative" | |
| st.markdown(f""" | |
| <div class="forecast-card"> | |
| <div class="forecast-card-header"> | |
| <h4 class="forecast-card-title">Tendance Détectée</h4> | |
| <span class="forecast-card-badge {tendance_badge_class}">{interpretations['tendance']}</span> | |
| </div> | |
| <div class="forecast-card-body"> | |
| <div class="forecast-card-row"> | |
| <span class="forecast-card-label">Variation Mensuelle Moyenne</span> | |
| <span class="forecast-card-value highlight">{abs(interpretations['pente_mensuelle']):,.0f} XOF/mois ({interpretations['pct_variation']:.1f}%)</span> | |
| </div> | |
| <div class="forecast-card-row"> | |
| <span class="forecast-card-label">Force de la Corrélation</span> | |
| <span class="forecast-card-value">{interpretations['force_correlation'].upper()}</span> | |
| </div> | |
| <div class="forecast-card-row"> | |
| <span class="forecast-card-label">Qualité du Modèle</span> | |
| <span class="forecast-card-value">{interpretations['qualite_modele'].upper()}</span> | |
| </div> | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Interprétation textuelle | |
| st.markdown(f""" | |
| <div class="interpretation-box"> | |
| <h4> Interprétation</h4> | |
| <p><strong>Tendance :</strong> {interpretations['message_principal']}</p> | |
| <p><strong>Fiabilité :</strong> {interpretations['message_fiabilite']}</p> | |
| <p><strong>Précision :</strong> {interpretations['message_precision']}</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Section 4 : Métriques de Performance du Modèle | |
| st.divider() | |
| st.subheader(" Performance du Modèle") | |
| col1, col2, col3, col4, col5 = st.columns(5) | |
| with col1: | |
| st.metric("R² (R-Carré)", f"{models['r_squared']:.3f}", | |
| help="Coefficient de détermination - Pourcentage de variance expliquée") | |
| with col2: | |
| st.metric("R (Corrélation)", f"{models['r_coefficient']:.3f}", | |
| help="Coefficient de corrélation") | |
| with col3: | |
| st.metric("MAE", f"{models['mae']:,.0f} XOF", | |
| help="Mean Absolute Error - Erreur moyenne absolue") | |
| with col4: | |
| st.metric("RMSE", f"{models['rmse']:,.0f} XOF", | |
| help="Root Mean Squared Error - Erreur quadratique moyenne") | |
| with col5: | |
| pente_jour = models['pente'] | |
| st.metric("Pente", f"{pente_jour:.2f} XOF/jour", | |
| help="Croissance par jour ordinal") | |
| # Section 5 : Visualisations | |
| st.divider() | |
| st.subheader(" Visualisations Analytiques") | |
| # Préparation des données de visualisation | |
| viz_data = analyzer.generate_visualization_data(predictions_result) | |
| # Graphique principal : Historique + Prédictions | |
| fig_main = go.Figure() | |
| # Données historiques | |
| fig_main.add_trace(go.Scatter( | |
| x=viz_data['historical']['dates'], | |
| y=viz_data['historical']['values'], | |
| mode='lines+markers', | |
| name='Flux Historiques', | |
| line=dict(color='#58a6ff', width=2), | |
| marker=dict(size=8, symbol='circle') | |
| )) | |
| # Prédictions futures | |
| fig_main.add_trace(go.Scatter( | |
| x=viz_data['future']['dates'], | |
| y=viz_data['future']['predictions'], | |
| mode='lines+markers', | |
| name='Prédictions', | |
| line=dict(color='#54bd4b', width=2, dash='dash'), | |
| marker=dict(size=10, symbol='diamond') | |
| )) | |
| # Intervalle de confiance | |
| fig_main.add_trace(go.Scatter( | |
| x=viz_data['future']['dates'] + viz_data['future']['dates'][::-1], | |
| y=viz_data['future']['upper_bound'] + viz_data['future']['lower_bound'][::-1], | |
| fill='toself', | |
| fillcolor='rgba(84, 189, 75, 0.2)', | |
| line=dict(color='rgba(84, 189, 75, 0)'), | |
| name='Intervalle de Confiance (95%)', | |
| showlegend=True | |
| )) | |
| fig_main.update_layout( | |
| title=dict( | |
| text="Flux de Sortie Mensuels : Historique & Prédictions", | |
| font=dict(size=16, color='#58a6ff') | |
| ), | |
| xaxis=dict( | |
| title="Date", | |
| gridcolor='rgba(48, 54, 61, 0.3)', | |
| color='#8b949e' | |
| ), | |
| yaxis=dict( | |
| title="Montant (XOF)", | |
| gridcolor='rgba(48, 54, 61, 0.3)', | |
| color='#8b949e' | |
| ), | |
| plot_bgcolor='rgba(13, 17, 23, 0.8)', | |
| paper_bgcolor='rgba(22, 27, 34, 0.9)', | |
| font=dict(color='#c9d1d9', family='Space Grotesk'), | |
| hovermode='x unified', | |
| legend=dict( | |
| bgcolor='rgba(22, 27, 34, 0.8)', | |
| bordercolor='rgba(88, 166, 255, 0.3)', | |
| borderwidth=1 | |
| ), | |
| height=500 | |
| ) | |
| st.plotly_chart(fig_main, use_container_width=True) | |
| # Graphiques secondaires : Résidus | |
| with st.expander(" Analyse des Résidus (Diagnostic du Modèle)", expanded=False): | |
| fig_residuals = make_subplots( | |
| rows=1, cols=2, | |
| subplot_titles=("Graphique des Résidus", "Distribution des Résidus"), | |
| specs=[[{"type": "scatter"}, {"type": "histogram"}]] | |
| ) | |
| # Graphique des résidus | |
| fig_residuals.add_trace( | |
| go.Scatter( | |
| x=viz_data['residuals']['predictions'], | |
| y=viz_data['residuals']['values'], | |
| mode='markers', | |
| marker=dict(color='#58a6ff', size=8), | |
| name='Résidus' | |
| ), | |
| row=1, col=1 | |
| ) | |
| # Ligne à zéro | |
| fig_residuals.add_hline( | |
| y=0, line_dash="dash", line_color="#f39c12", | |
| row=1, col=1 | |
| ) | |
| # Distribution des résidus | |
| fig_residuals.add_trace( | |
| go.Histogram( | |
| x=viz_data['residuals']['values'], | |
| marker=dict(color='#58a6ff', line=dict(color='#c9d1d9', width=1)), | |
| name='Distribution', | |
| nbinsx=15 | |
| ), | |
| row=1, col=2 | |
| ) | |
| fig_residuals.update_xaxes(title_text="Valeurs Prédites (XOF)", row=1, col=1, gridcolor='rgba(48, 54, 61, 0.3)', color='#8b949e') | |
| fig_residuals.update_yaxes(title_text="Résidus (XOF)", row=1, col=1, gridcolor='rgba(48, 54, 61, 0.3)', color='#8b949e') | |
| fig_residuals.update_xaxes(title_text="Résidus (XOF)", row=1, col=2, gridcolor='rgba(48, 54, 61, 0.3)', color='#8b949e') | |
| fig_residuals.update_yaxes(title_text="Fréquence", row=1, col=2, gridcolor='rgba(48, 54, 61, 0.3)', color='#8b949e') | |
| fig_residuals.update_layout( | |
| plot_bgcolor='rgba(13, 17, 23, 0.8)', | |
| paper_bgcolor='rgba(22, 27, 34, 0.9)', | |
| font=dict(color='#c9d1d9', family='Space Grotesk'), | |
| showlegend=False, | |
| height=400 | |
| ) | |
| st.plotly_chart(fig_residuals, use_container_width=True) | |
| st.caption("**Note :** Les résidus doivent être aléatoirement distribués autour de zéro pour un bon modèle.") | |
| # Section 6 : Tableau des données | |
| with st.expander(" Tableau Détaillé des Prédictions", expanded=False): | |
| # Formatage du DataFrame pour l'affichage | |
| display_df = predictions_df.copy() | |
| display_df['Date'] = display_df['Date'].dt.strftime('%B %Y') | |
| display_df['Montant_Predit'] = display_df['Montant_Predit'].apply(lambda x: f"{x:,.0f} XOF") | |
| display_df['Borne_Inf'] = display_df['Borne_Inf'].apply(lambda x: f"{x:,.0f} XOF") | |
| display_df['Borne_Sup'] = display_df['Borne_Sup'].apply(lambda x: f"{x:,.0f} XOF") | |
| display_df.columns = ['Date', 'Montant Prédit', 'Borne Inférieure (95%)', 'Borne Supérieure (95%)'] | |
| st.dataframe(display_df, use_container_width=True, hide_index=True) | |
| # Section 7 : Informations complémentaires | |
| st.divider() | |
| st.info(""" | |
| **ℹ️ À Propos de ce Modèle** | |
| - **Modèle utilisé :** Régression Linéaire avec Intervalles de Confiance à 95% | |
| - **Actualisation :** Les prédictions se mettent à jour automatiquement à chaque ajout de données dans la feuille "Forecasting" | |
| - **Utilisation :** Ce modèle est adapté pour des tendances linéaires. Pour des patterns saisonniers complexes, envisagez des modèles avancés (SARIMA, Prophet) | |
| - **Recommandation :** Vérifiez régulièrement les résidus et le R² pour assurer la qualité du modèle | |
| """) | |
| except Exception as e: | |
| st.error(f"❌ Erreur lors du chargement des données : {e}") | |
| st.exception(e) | |
| st.markdown('</div>', unsafe_allow_html=True) |