klydekushy commited on
Commit
39cc45c
·
verified ·
1 Parent(s): 398ed20

Update src/modules/Forcasting.py

Browse files
Files changed (1) hide show
  1. src/modules/Forcasting.py +625 -0
src/modules/Forcasting.py CHANGED
@@ -0,0 +1,625 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import sys
4
+ import os
5
+ import plotly.graph_objects as go
6
+ from plotly.subplots import make_subplots
7
+ import numpy as np
8
+
9
+ # Import du module d'analyse
10
+ sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
11
+ from Analytics.VortexOutFlux import VortexOutFlux
12
+
13
+ # === CSS SPÉCIFIQUE MODULE FORECASTING ===
14
+ def apply_forecasting_styles():
15
+ st.markdown("""
16
+ <style>
17
+ /* === STYLES SPÉCIFIQUES MODULE FORECASTING === */
18
+
19
+ /* Wrapper pour isolation */
20
+ #forecasting-module {
21
+ font-family: 'Space Grotesk', sans-serif;
22
+ }
23
+
24
+ /* Headers spécifiques */
25
+ #forecasting-module h1 {
26
+ font-size: 1.8rem !important;
27
+ margin-bottom: 24px !important;
28
+ color: #58a6ff !important;
29
+ border-bottom: 2px solid rgba(88, 166, 255, 0.3);
30
+ padding-bottom: 12px;
31
+ }
32
+
33
+ #forecasting-module h2 {
34
+ font-size: 1.4rem !important;
35
+ margin-bottom: 16px !important;
36
+ color: #8b949e !important;
37
+ }
38
+
39
+ #forecasting-module h3 {
40
+ font-size: 1.1rem !important;
41
+ margin-bottom: 12px !important;
42
+ color: #58a6ff !important;
43
+ }
44
+
45
+ /* Metrics cards pour KPIs */
46
+ #forecasting-module [data-testid="stMetric"] {
47
+ background: rgba(22, 27, 34, 0.7);
48
+ border: 1px solid rgba(88, 166, 255, 0.4);
49
+ padding: 16px !important;
50
+ border-radius: 6px;
51
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
52
+ }
53
+
54
+ #forecasting-module [data-testid="stMetric"] label {
55
+ color: #8b949e !important;
56
+ font-size: 0.75rem !important;
57
+ font-weight: 600 !important;
58
+ text-transform: uppercase;
59
+ letter-spacing: 0.8px;
60
+ }
61
+
62
+ #forecasting-module [data-testid="stMetric"] [data-testid="stMetricValue"] {
63
+ color: #58a6ff !important;
64
+ font-size: 1.6rem !important;
65
+ font-weight: 700 !important;
66
+ }
67
+
68
+ #forecasting-module [data-testid="stMetric"] [data-testid="stMetricDelta"] {
69
+ color: #54bd4b !important;
70
+ font-size: 0.9rem !important;
71
+ }
72
+
73
+ /* Carte de prédiction style Gotham */
74
+ .forecast-card {
75
+ background: linear-gradient(135deg, rgba(22, 27, 34, 0.9) 0%, rgba(13, 17, 23, 0.9) 100%);
76
+ border: 2px solid rgba(88, 166, 255, 0.3);
77
+ border-radius: 8px;
78
+ padding: 20px;
79
+ margin: 16px 0;
80
+ transition: all 0.3s ease;
81
+ position: relative;
82
+ overflow: hidden;
83
+ }
84
+
85
+ .forecast-card:hover {
86
+ border-color: rgba(88, 166, 255, 0.6);
87
+ transform: translateY(-2px);
88
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.4);
89
+ }
90
+
91
+ .forecast-card::before {
92
+ content: '';
93
+ position: absolute;
94
+ top: 0;
95
+ left: 0;
96
+ right: 0;
97
+ height: 3px;
98
+ background: linear-gradient(90deg, #58a6ff, #54bd4b);
99
+ }
100
+
101
+ .forecast-card-header {
102
+ display: flex;
103
+ justify-content: space-between;
104
+ align-items: center;
105
+ margin-bottom: 16px;
106
+ padding-bottom: 12px;
107
+ border-bottom: 1px solid rgba(88, 166, 255, 0.2);
108
+ }
109
+
110
+ .forecast-card-title {
111
+ font-size: 1.2rem;
112
+ font-weight: 700;
113
+ color: #58a6ff;
114
+ margin: 0;
115
+ }
116
+
117
+ .forecast-card-badge {
118
+ background: rgba(88, 166, 255, 0.2);
119
+ color: #58a6ff;
120
+ padding: 4px 12px;
121
+ border-radius: 12px;
122
+ font-size: 0.75rem;
123
+ font-weight: 600;
124
+ text-transform: uppercase;
125
+ }
126
+
127
+ .forecast-card-badge.positive {
128
+ background: rgba(84, 189, 75, 0.2);
129
+ color: #54bd4b;
130
+ }
131
+
132
+ .forecast-card-badge.negative {
133
+ background: rgba(243, 156, 18, 0.2);
134
+ color: #f39c12;
135
+ }
136
+
137
+ .forecast-card-body {
138
+ color: #c9d1d9;
139
+ }
140
+
141
+ .forecast-card-row {
142
+ display: flex;
143
+ justify-content: space-between;
144
+ padding: 10px 0;
145
+ border-bottom: 1px solid rgba(48, 54, 61, 0.4);
146
+ }
147
+
148
+ .forecast-card-row:last-child {
149
+ border-bottom: none;
150
+ }
151
+
152
+ .forecast-card-label {
153
+ color: #8b949e;
154
+ font-size: 0.9rem;
155
+ font-weight: 500;
156
+ }
157
+
158
+ .forecast-card-value {
159
+ color: #c9d1d9;
160
+ font-weight: 600;
161
+ font-size: 1rem;
162
+ }
163
+
164
+ .forecast-card-value.highlight {
165
+ color: #58a6ff;
166
+ font-size: 1.2rem;
167
+ }
168
+
169
+ /* Bloc d'interprétation */
170
+ .interpretation-box {
171
+ background: rgba(88, 166, 255, 0.05);
172
+ border-left: 4px solid #58a6ff;
173
+ border-radius: 4px;
174
+ padding: 16px;
175
+ margin: 16px 0;
176
+ }
177
+
178
+ .interpretation-box h4 {
179
+ color: #58a6ff !important;
180
+ margin: 0 0 12px 0 !important;
181
+ font-size: 1rem !important;
182
+ }
183
+
184
+ .interpretation-box p {
185
+ color: #c9d1d9;
186
+ margin: 8px 0;
187
+ line-height: 1.6;
188
+ font-size: 0.9rem;
189
+ }
190
+
191
+ /* Bloc métriques de performance */
192
+ .metrics-performance {
193
+ background: rgba(22, 27, 34, 0.6);
194
+ border: 1px solid rgba(48, 54, 61, 0.8);
195
+ border-radius: 6px;
196
+ padding: 16px;
197
+ margin: 16px 0;
198
+ }
199
+
200
+ .metrics-grid {
201
+ display: grid;
202
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
203
+ gap: 16px;
204
+ margin-top: 12px;
205
+ }
206
+
207
+ .metric-item {
208
+ background: rgba(13, 17, 23, 0.8);
209
+ padding: 12px;
210
+ border-radius: 4px;
211
+ border-left: 3px solid #58a6ff;
212
+ }
213
+
214
+ .metric-item-label {
215
+ color: #8b949e;
216
+ font-size: 0.75rem;
217
+ text-transform: uppercase;
218
+ letter-spacing: 0.5px;
219
+ margin-bottom: 4px;
220
+ }
221
+
222
+ .metric-item-value {
223
+ color: #58a6ff;
224
+ font-size: 1.3rem;
225
+ font-weight: 700;
226
+ }
227
+
228
+ /* Divider */
229
+ #forecasting-module hr {
230
+ background: rgba(88, 166, 255, 0.3);
231
+ height: 2px;
232
+ margin: 24px 0;
233
+ }
234
+
235
+ /* Alert boxes */
236
+ #forecasting-module .stAlert {
237
+ border-radius: 6px;
238
+ padding: 12px 16px !important;
239
+ }
240
+
241
+ #forecasting-module .stAlert[kind="info"] {
242
+ background: rgba(88, 166, 255, 0.1) !important;
243
+ border-left: 4px solid #58a6ff !important;
244
+ }
245
+
246
+ #forecasting-module .stAlert[kind="warning"] {
247
+ background: rgba(243, 156, 18, 0.1) !important;
248
+ border-left: 4px solid #f39c12 !important;
249
+ }
250
+
251
+ #forecasting-module .stAlert[kind="success"] {
252
+ background: rgba(84, 189, 75, 0.1) !important;
253
+ border-left: 4px solid #54bd4b !important;
254
+ }
255
+
256
+ /* Expanders */
257
+ #forecasting-module .streamlit-expanderHeader {
258
+ background: rgba(22, 27, 34, 0.7) !important;
259
+ border-left: 3px solid rgba(88, 166, 255, 0.6) !important;
260
+ padding: 12px 16px !important;
261
+ font-weight: 600 !important;
262
+ color: #8b949e !important;
263
+ }
264
+
265
+ #forecasting-module .streamlit-expanderHeader:hover {
266
+ background: rgba(33, 38, 45, 0.9) !important;
267
+ border-left-color: rgba(88, 166, 255, 0.9) !important;
268
+ }
269
+
270
+ #forecasting-module .streamlit-expanderContent {
271
+ background: rgba(13, 17, 23, 0.7);
272
+ border: 1px solid rgba(48, 54, 61, 0.6);
273
+ padding: 16px !important;
274
+ }
275
+
276
+ /* Animation de chargement */
277
+ @keyframes pulse {
278
+ 0%, 100% { opacity: 1; }
279
+ 50% { opacity: 0.5; }
280
+ }
281
+
282
+ .loading-indicator {
283
+ animation: pulse 2s ease-in-out infinite;
284
+ color: #58a6ff;
285
+ text-align: center;
286
+ padding: 20px;
287
+ }
288
+ </style>
289
+ """, unsafe_allow_html=True)
290
+
291
+ def render_forecast_card(month_data):
292
+ """Génère une carte de prédiction mensuelle style Gotham"""
293
+
294
+ date_str = month_data['Date'].strftime("%B %Y")
295
+ montant = month_data['Montant_Predit']
296
+ lower = month_data['Borne_Inf']
297
+ upper = month_data['Borne_Sup']
298
+
299
+ # Calcul de la marge d'erreur
300
+ margin = ((upper - lower) / 2) / montant * 100 if montant > 0 else 0
301
+
302
+ html = f"""
303
+ <div class="forecast-card">
304
+ <div class="forecast-card-header">
305
+ <h4 class="forecast-card-title">{date_str}</h4>
306
+ <span class="forecast-card-badge">Prédiction</span>
307
+ </div>
308
+ <div class="forecast-card-body">
309
+ <div class="forecast-card-row">
310
+ <span class="forecast-card-label">Montant Prédit</span>
311
+ <span class="forecast-card-value highlight">{montant:,.0f} XOF</span>
312
+ </div>
313
+ <div class="forecast-card-row">
314
+ <span class="forecast-card-label">Intervalle de Confiance (95%)</span>
315
+ <span class="forecast-card-value">{lower:,.0f} - {upper:,.0f} XOF</span>
316
+ </div>
317
+ <div class="forecast-card-row">
318
+ <span class="forecast-card-label">Marge d'Erreur</span>
319
+ <span class="forecast-card-value">± {margin:.1f}%</span>
320
+ </div>
321
+ </div>
322
+ </div>
323
+ """
324
+ return html
325
+
326
+ def show_forecasting_module(client, sheet_name):
327
+ """Module principal de prévision des flux de sortie"""
328
+
329
+ # Appliquer les styles
330
+ apply_forecasting_styles()
331
+
332
+ # Wrapper pour isolation
333
+ st.markdown('<div id="forecasting-module">', unsafe_allow_html=True)
334
+
335
+ st.header("JASMINE - PRÉDICTION DES FLUX DE SORTIE")
336
+ st.caption("Analyse prédictive en temps réel basée sur la régression linéaire")
337
+
338
+ try:
339
+ # Connexion à Google Sheets
340
+ sh = client.open(sheet_name)
341
+ ws_forecasting = sh.worksheet("Forecasting")
342
+
343
+ # Chargement des données
344
+ df_forecasting = pd.DataFrame(ws_forecasting.get_all_records())
345
+
346
+ if df_forecasting.empty or len(df_forecasting) < 2:
347
+ st.warning("⚠️ Données insuffisantes pour effectuer une prédiction. Minimum 2 points de données requis.")
348
+ st.markdown('</div>', unsafe_allow_html=True)
349
+ return
350
+
351
+ # Vérification des colonnes
352
+ if 'Date' not in df_forecasting.columns or 'Montant_Total_Sortie' not in df_forecasting.columns:
353
+ st.error("❌ Structure de données invalide. Colonnes requises : 'Date', 'Montant_Total_Sortie'")
354
+ st.markdown('</div>', unsafe_allow_html=True)
355
+ return
356
+
357
+ # Initialisation de l'analyseur
358
+ analyzer = VortexOutFlux(df_forecasting)
359
+
360
+ # Section 1 : Vue d'ensemble des données
361
+ st.divider()
362
+ st.subheader("📊 Vue d'Ensemble des Données Historiques")
363
+
364
+ col1, col2, col3, col4 = st.columns(4)
365
+
366
+ with col1:
367
+ nb_points = len(analyzer.df)
368
+ st.metric("Points de Données", nb_points)
369
+
370
+ with col2:
371
+ flux_moyen = analyzer.df['Montant_Total_Sortie'].mean()
372
+ st.metric("Flux Moyen", f"{flux_moyen:,.0f} XOF")
373
+
374
+ with col3:
375
+ flux_total = analyzer.df['Montant_Total_Sortie'].sum()
376
+ st.metric("Flux Total", f"{flux_total:,.0f} XOF")
377
+
378
+ with col4:
379
+ date_debut = analyzer.df['Date'].min().strftime("%m/%Y")
380
+ date_fin = analyzer.df['Date'].max().strftime("%m/%Y")
381
+ st.metric("Période", f"{date_debut} - {date_fin}")
382
+
383
+ # Section 2 : Prédictions
384
+ st.divider()
385
+ st.subheader("🔮 Prédictions pour les 3 Prochains Mois")
386
+
387
+ with st.spinner("Calcul des prédictions en cours..."):
388
+ predictions_result = analyzer.predict_next_months(n_months=3)
389
+
390
+ if 'error' in predictions_result:
391
+ st.error(f"❌ {predictions_result['error']}")
392
+ st.markdown('</div>', unsafe_allow_html=True)
393
+ return
394
+
395
+ predictions_df = predictions_result['predictions']
396
+ models = predictions_result['models']
397
+
398
+ # Affichage des cartes de prédiction
399
+ cols = st.columns(3)
400
+ for idx, (_, row) in enumerate(predictions_df.iterrows()):
401
+ with cols[idx]:
402
+ st.markdown(render_forecast_card(row), unsafe_allow_html=True)
403
+
404
+ # Section 3 : Analyse de Tendance et Interprétation
405
+ st.divider()
406
+ st.subheader("📈 Analyse de Tendance")
407
+
408
+ interpretations = analyzer.get_interpretation(models)
409
+
410
+ # Affichage de la tendance principale
411
+ tendance_badge_class = "positive" if interpretations['tendance'] == "CROISSANCE" else "negative"
412
+
413
+ st.markdown(f"""
414
+ <div class="forecast-card">
415
+ <div class="forecast-card-header">
416
+ <h4 class="forecast-card-title">Tendance Détectée</h4>
417
+ <span class="forecast-card-badge {tendance_badge_class}">{interpretations['tendance']}</span>
418
+ </div>
419
+ <div class="forecast-card-body">
420
+ <div class="forecast-card-row">
421
+ <span class="forecast-card-label">Variation Mensuelle Moyenne</span>
422
+ <span class="forecast-card-value highlight">{abs(interpretations['pente_mensuelle']):,.0f} XOF/mois ({interpretations['pct_variation']:.1f}%)</span>
423
+ </div>
424
+ <div class="forecast-card-row">
425
+ <span class="forecast-card-label">Force de la Corrélation</span>
426
+ <span class="forecast-card-value">{interpretations['force_correlation'].upper()}</span>
427
+ </div>
428
+ <div class="forecast-card-row">
429
+ <span class="forecast-card-label">Qualité du Modèle</span>
430
+ <span class="forecast-card-value">{interpretations['qualite_modele'].upper()}</span>
431
+ </div>
432
+ </div>
433
+ </div>
434
+ """, unsafe_allow_html=True)
435
+
436
+ # Interprétation textuelle
437
+ st.markdown(f"""
438
+ <div class="interpretation-box">
439
+ <h4>💡 Interprétation</h4>
440
+ <p><strong>Tendance :</strong> {interpretations['message_principal']}</p>
441
+ <p><strong>Fiabilité :</strong> {interpretations['message_fiabilite']}</p>
442
+ <p><strong>Précision :</strong> {interpretations['message_precision']}</p>
443
+ </div>
444
+ """, unsafe_allow_html=True)
445
+
446
+ # Section 4 : Métriques de Performance du Modèle
447
+ st.divider()
448
+ st.subheader("🎯 Performance du Modèle")
449
+
450
+ col1, col2, col3, col4, col5 = st.columns(5)
451
+
452
+ with col1:
453
+ st.metric("R² (R-Carré)", f"{models['r_squared']:.3f}",
454
+ help="Coefficient de détermination - Pourcentage de variance expliquée")
455
+
456
+ with col2:
457
+ st.metric("R (Corrélation)", f"{models['r_coefficient']:.3f}",
458
+ help="Coefficient de corrélation")
459
+
460
+ with col3:
461
+ st.metric("MAE", f"{models['mae']:,.0f} XOF",
462
+ help="Mean Absolute Error - Erreur moyenne absolue")
463
+
464
+ with col4:
465
+ st.metric("RMSE", f"{models['rmse']:,.0f} XOF",
466
+ help="Root Mean Squared Error - Erreur quadratique moyenne")
467
+
468
+ with col5:
469
+ pente_jour = models['pente']
470
+ st.metric("Pente", f"{pente_jour:.2f} XOF/jour",
471
+ help="Croissance par jour ordinal")
472
+
473
+ # Section 5 : Visualisations
474
+ st.divider()
475
+ st.subheader("📉 Visualisations Analytiques")
476
+
477
+ # Préparation des données de visualisation
478
+ viz_data = analyzer.generate_visualization_data(predictions_result)
479
+
480
+ # Graphique principal : Historique + Prédictions
481
+ fig_main = go.Figure()
482
+
483
+ # Données historiques
484
+ fig_main.add_trace(go.Scatter(
485
+ x=viz_data['historical']['dates'],
486
+ y=viz_data['historical']['values'],
487
+ mode='lines+markers',
488
+ name='Flux Historiques',
489
+ line=dict(color='#58a6ff', width=2),
490
+ marker=dict(size=8, symbol='circle')
491
+ ))
492
+
493
+ # Prédictions futures
494
+ fig_main.add_trace(go.Scatter(
495
+ x=viz_data['future']['dates'],
496
+ y=viz_data['future']['predictions'],
497
+ mode='lines+markers',
498
+ name='Prédictions',
499
+ line=dict(color='#54bd4b', width=2, dash='dash'),
500
+ marker=dict(size=10, symbol='diamond')
501
+ ))
502
+
503
+ # Intervalle de confiance
504
+ fig_main.add_trace(go.Scatter(
505
+ x=viz_data['future']['dates'] + viz_data['future']['dates'][::-1],
506
+ y=viz_data['future']['upper_bound'] + viz_data['future']['lower_bound'][::-1],
507
+ fill='toself',
508
+ fillcolor='rgba(84, 189, 75, 0.2)',
509
+ line=dict(color='rgba(84, 189, 75, 0)'),
510
+ name='Intervalle de Confiance (95%)',
511
+ showlegend=True
512
+ ))
513
+
514
+ fig_main.update_layout(
515
+ title=dict(
516
+ text="Flux de Sortie Mensuels : Historique & Prédictions",
517
+ font=dict(size=16, color='#58a6ff')
518
+ ),
519
+ xaxis=dict(
520
+ title="Date",
521
+ gridcolor='rgba(48, 54, 61, 0.3)',
522
+ color='#8b949e'
523
+ ),
524
+ yaxis=dict(
525
+ title="Montant (XOF)",
526
+ gridcolor='rgba(48, 54, 61, 0.3)',
527
+ color='#8b949e'
528
+ ),
529
+ plot_bgcolor='rgba(13, 17, 23, 0.8)',
530
+ paper_bgcolor='rgba(22, 27, 34, 0.9)',
531
+ font=dict(color='#c9d1d9', family='Space Grotesk'),
532
+ hovermode='x unified',
533
+ legend=dict(
534
+ bgcolor='rgba(22, 27, 34, 0.8)',
535
+ bordercolor='rgba(88, 166, 255, 0.3)',
536
+ borderwidth=1
537
+ ),
538
+ height=500
539
+ )
540
+
541
+ st.plotly_chart(fig_main, use_container_width=True)
542
+
543
+ # Graphiques secondaires : Résidus
544
+ with st.expander("📊 Analyse des Résidus (Diagnostic du Modèle)", expanded=False):
545
+ fig_residuals = make_subplots(
546
+ rows=1, cols=2,
547
+ subplot_titles=("Graphique des Résidus", "Distribution des Résidus"),
548
+ specs=[[{"type": "scatter"}, {"type": "histogram"}]]
549
+ )
550
+
551
+ # Graphique des résidus
552
+ fig_residuals.add_trace(
553
+ go.Scatter(
554
+ x=viz_data['residuals']['predictions'],
555
+ y=viz_data['residuals']['values'],
556
+ mode='markers',
557
+ marker=dict(color='#58a6ff', size=8),
558
+ name='Résidus'
559
+ ),
560
+ row=1, col=1
561
+ )
562
+
563
+ # Ligne à zéro
564
+ fig_residuals.add_hline(
565
+ y=0, line_dash="dash", line_color="#f39c12",
566
+ row=1, col=1
567
+ )
568
+
569
+ # Distribution des résidus
570
+ fig_residuals.add_trace(
571
+ go.Histogram(
572
+ x=viz_data['residuals']['values'],
573
+ marker=dict(color='#58a6ff', line=dict(color='#c9d1d9', width=1)),
574
+ name='Distribution',
575
+ nbinsx=15
576
+ ),
577
+ row=1, col=2
578
+ )
579
+
580
+ fig_residuals.update_xaxes(title_text="Valeurs Prédites (XOF)", row=1, col=1, gridcolor='rgba(48, 54, 61, 0.3)', color='#8b949e')
581
+ fig_residuals.update_yaxes(title_text="Résidus (XOF)", row=1, col=1, gridcolor='rgba(48, 54, 61, 0.3)', color='#8b949e')
582
+ fig_residuals.update_xaxes(title_text="Résidus (XOF)", row=1, col=2, gridcolor='rgba(48, 54, 61, 0.3)', color='#8b949e')
583
+ fig_residuals.update_yaxes(title_text="Fréquence", row=1, col=2, gridcolor='rgba(48, 54, 61, 0.3)', color='#8b949e')
584
+
585
+ fig_residuals.update_layout(
586
+ plot_bgcolor='rgba(13, 17, 23, 0.8)',
587
+ paper_bgcolor='rgba(22, 27, 34, 0.9)',
588
+ font=dict(color='#c9d1d9', family='Space Grotesk'),
589
+ showlegend=False,
590
+ height=400
591
+ )
592
+
593
+ st.plotly_chart(fig_residuals, use_container_width=True)
594
+
595
+ st.caption("**Note :** Les résidus doivent être aléatoirement distribués autour de zéro pour un bon modèle.")
596
+
597
+ # Section 6 : Tableau des données
598
+ with st.expander("📋 Tableau Détaillé des Prédictions", expanded=False):
599
+ # Formatage du DataFrame pour l'affichage
600
+ display_df = predictions_df.copy()
601
+ display_df['Date'] = display_df['Date'].dt.strftime('%B %Y')
602
+ display_df['Montant_Predit'] = display_df['Montant_Predit'].apply(lambda x: f"{x:,.0f} XOF")
603
+ display_df['Borne_Inf'] = display_df['Borne_Inf'].apply(lambda x: f"{x:,.0f} XOF")
604
+ display_df['Borne_Sup'] = display_df['Borne_Sup'].apply(lambda x: f"{x:,.0f} XOF")
605
+
606
+ display_df.columns = ['Date', 'Montant Prédit', 'Borne Inférieure (95%)', 'Borne Supérieure (95%)']
607
+
608
+ st.dataframe(display_df, use_container_width=True, hide_index=True)
609
+
610
+ # Section 7 : Informations complémentaires
611
+ st.divider()
612
+ st.info("""
613
+ **ℹ️ À Propos de ce Modèle**
614
+
615
+ - **Modèle utilisé :** Régression Linéaire avec Intervalles de Confiance à 95%
616
+ - **Actualisation :** Les prédictions se mettent à jour automatiquement à chaque ajout de données dans la feuille "Forecasting"
617
+ - **Utilisation :** Ce modèle est adapté pour des tendances linéaires. Pour des patterns saisonniers complexes, envisagez des modèles avancés (SARIMA, Prophet)
618
+ - **Recommandation :** Vérifiez régulièrement les résidus et le R² pour assurer la qualité du modèle
619
+ """)
620
+
621
+ except Exception as e:
622
+ st.error(f"❌ Erreur lors du chargement des données : {e}")
623
+ st.exception(e)
624
+
625
+ st.markdown('</div>', unsafe_allow_html=True)