File size: 17,080 Bytes
3f43f3d
 
 
83e717b
9a748f4
 
 
 
 
 
 
 
c605521
3f43f3d
 
 
97fc5da
3f43f3d
 
 
 
9a748f4
3f43f3d
9a748f4
3f43f3d
6d58631
97fc5da
3f43f3d
9a748f4
 
a49af7f
 
 
 
 
 
 
 
b14ae08
3f43f3d
97fc5da
3f43f3d
 
 
b14ae08
 
883ca59
 
 
 
 
 
 
 
00d8855
 
 
 
 
 
 
 
 
5032c40
 
 
 
 
 
 
c08610b
 
 
 
 
 
 
c605521
 
 
 
 
 
 
3f43f3d
 
 
e63fa12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9a748f4
e63fa12
 
 
9a748f4
 
e2daa46
 
 
 
 
 
 
 
 
 
9a748f4
 
e2daa46
 
 
 
 
 
 
 
 
 
 
 
9a748f4
e2daa46
9a748f4
 
b14ae08
 
 
 
 
 
 
9a748f4
 
 
 
 
 
b14ae08
9a748f4
 
b14ae08
 
18e1082
b14ae08
 
 
 
 
 
e63fa12
9a748f4
e63fa12
b14ae08
e63fa12
daa6ace
 
 
 
 
 
 
 
 
 
 
 
e63fa12
 
fff4768
6d58631
e63fa12
00d8855
e63fa12
b14ae08
 
c605521
b14ae08
 
 
 
 
 
daa6ace
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63928cb
 
 
daa6ace
 
e63fa12
b14ae08
 
e63fa12
 
9a748f4
83e717b
e63fa12
b14ae08
a49af7f
 
 
 
 
 
 
 
b14ae08
 
e706b82
b14ae08
e706b82
b14ae08
e706b82
 
 
 
 
 
 
b14ae08
e706b82
b14ae08
e706b82
 
 
 
b14ae08
 
61cef30
b5a5eec
d12d4f1
61cef30
 
 
 
 
b5a5eec
d12d4f1
b5a5eec
d12d4f1
61cef30
fe54e0d
 
61cef30
 
 
b5a5eec
 
61cef30
b5a5eec
61cef30
b5a5eec
61cef30
b5a5eec
61cef30
b5a5eec
61cef30
 
 
 
e63fa12
 
1e601a6
883ca59
1e601a6
 
883ca59
 
 
 
 
 
00d8855
b3c42c9
1e601a6
5032c40
 
 
 
 
 
 
 
 
 
c08610b
 
 
 
 
 
 
 
 
 
 
c605521
 
 
 
 
 
 
 
 
 
 
 
00d8855
 
 
 
4849684
 
00d8855
4849684
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2070ea3
00d8855
 
 
4849684
b14ae08
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e63fa12
d872319
b14ae08
d872319
 
 
1
2
3
4
5
6
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
# ==============================================================================
# CORRECTIF IMPORTS - Remplace tout le début de ton fichier jusqu'à "apply_vortex_style"
# ==============================================================================
import streamlit as st
import pandas as pd
import gspread
from google.oauth2.service_account import Credentials
import os
import json
import sys
import time


# --- 1. CONFIGURATION ROBUSTE DES CHEMINS ---
# Récupère le dossier où se trouve CE fichier (streamlit_app.py)
current_dir = os.path.dirname(os.path.abspath(__file__))

# On ajoute ce dossier au chemin de recherche de Python
# Ainsi, Python peut voir le dossier "module" qui est juste à côté
if current_dir not in sys.path:
    sys.path.append(current_dir)

# --- 2. IMPORTS AVEC DEBUG ---
try:
    # On importe depuis "module" (singulier) car le dossier s'appelle "module"
    from module import AIP
except ImportError as e:
    st.error(f"❌ Erreur Import AIP : {e}") # Affiche la vraie erreur
    AIP = None


try:
    from module import Dashboard  # <--- AJOUT
except ImportError as e:
    st.error(f"❌ Erreur Import Dashboard : {e}")
    Dashboard = None

    
try:
    from module import Data_Exploration
except ImportError as e:
    # C'est ici que tu verras exactement pourquoi ça plante
    st.error(f"❌ Erreur Import Data_Exploration : {e}") 
    st.code(f"Chemin actuel: {current_dir}\nContenu du dossier: {os.listdir(current_dir)}")
    Data_Exploration = None

# Dans src/streamlit_app.py, section imports :

try:
    from module import Indicateurs_DQ  # <--- AJOUTER CETTE LIGNE
except ImportError as e:
    st.error(f"❌ Erreur Import Indicateurs_DQ : {e}")
    Indicateurs_DQ = None



# Module Validation Contract (ETL)
try:
    from module import Validation_Contract
except ImportError as e:
    st.error(f"❌ Erreur Import Validation_Contract : {e}")
    Validation_Contract = None


try:
    from module import Features  # <--- AJOUT
except ImportError as e:
    st.error(f"❌ Erreur Import Features : {e}")
    Features = None


try:
    from module import Forecasting # <--- AJOUT
except ImportError as e:
    st.error(f"❌ Erreur Import Forecasting : {e}")
    Forecasting = None


try:
    from module import add_data
except ImportError as e:
    st.error(f"❌ Erreur Import add_data : {e}")
    add_data = None

# ==============================================================================
# ... La suite de ton code (apply_vortex_style, etc.) ...

# ==============================================================================
# 1. FONCTION DE CONFIGURATION ET STYLE (GOTHAM SURVEILLANCE)
# ==============================================================================
def apply_vortex_style():
    """Injection du CSS complet Gotham Surveillance"""
    st.markdown("""
    <style>
    @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap');

    /* === FOND GLOBAL === */
    .stApp {
        background: linear-gradient(135deg, #0d1117 0%, #161b22 50%, #1c2128 100%);
        color: #c9d1d9;
        font-family: 'Space Grotesk', sans-serif;
    }

    /* === SIDEBAR STYLE === */
    [data-testid="stSidebar"] {
        background: linear-gradient(180deg, #161b22 0%, #1c2128 100%);
        border-right: 2px solid rgba(88, 166, 255, 0.3);
    }

    /* Titre sidebar */
    [data-testid="stSidebar"] h1 {
        color: #58a6ff !important;
        font-family: 'Space Grotesk', sans-serif !important;
        font-weight: 700 !important;
        text-transform: uppercase;
    }

    /* === CORRECTION RADIO BUTTONS === */
    [data-testid="stSidebar"] .stRadio [data-baseweb="radio"] > div:first-child { display: none !important; }
    [data-testid="stSidebar"] .stRadio label input + div {
        display: flex !important;
        align-items: center !important;
        background-color: transparent !important;
        color: #8b949e !important;
    }
    [data-testid="stSidebar"] .stRadio label input + div::before {
        content: "";
        display: inline-block;
        width: 14px; height: 14px;
        border: 1px solid #8b949e;
        background-color: rgba(22, 27, 34, 0.8);
        border-radius: 3px;
        margin-right: 12px;
        transition: all 0.2s ease;
    }
    [data-testid="stSidebar"] .stRadio label input:checked + div::before {
        background-color: #58a6ff !important;
        border-color: #58a6ff !important;
    }
    [data-testid="stSidebar"] .stRadio label input:checked + div { color: #58a6ff !important; }

    /* === HEADERS & TEXT === */
    h1, h2, h3 { color: #58a6ff !important; font-family: 'Space Grotesk', sans-serif !important; }
    h1 { font-size: 1.8rem !important; border-bottom: 1px solid rgba(88, 166, 255, 0.2); padding-bottom: 12px; }

    /* === TERMINAL / CHAT MESSAGES === */
    .user-msg { 
        background: rgba(30, 136, 229, 0.1); 
        border-left: 3px solid #1E88E5; 
        padding: 12px; margin: 10px 0; 
        border-radius: 0 4px 4px 0; 
    }
    .bot-msg { 
        background: rgba(48, 54, 61, 0.3); 
        border-left: 3px solid #00E676; 
        padding: 12px; margin: 10px 0; 
        border-radius: 0 4px 4px 0; 
    }

    /* Scrollbars */
    ::-webkit-scrollbar { width: 6px; }
    ::-webkit-scrollbar-thumb { background: rgba(88, 166, 255, 0.3); border-radius: 10px; }
    </style>
    """, unsafe_allow_html=True)

# ==============================================================================
# 2. MOTEUR DE CONNEXION
# ==============================================================================
@st.cache_resource
def get_gspread_client():
    # Essayer d'abord les variables d'environnement
    raw_json = os.environ.get("GSHEETS_JSON")
    
    # Si pas trouvé, essayer secrets.toml
    if not raw_json:
        try:
            raw_json = st.secrets.get("GSHEETS_JSON")
        except:
            raw_json = None
    
    if not raw_json:
        st.error("🛑 Secret 'GSHEETS_JSON' manquant.")
        st.info("""
        **Deux options pour configurer :**
        
        **Option 1 - Variable d'environnement :**
```bash
        export GSHEETS_JSON='{"type": "service_account", ...}'
```
        
        **Option 2 - Fichier secrets.toml :**
        1. Créez `.streamlit/secrets.toml`
        2. Ajoutez votre JSON Google Sheets
        """)
        return None
    
    try:
        creds_dict = json.loads(raw_json)
        credentials = Credentials.from_service_account_info(
            creds_dict, 
            scopes=[
                "https://www.googleapis.com/auth/spreadsheets", 
                "https://www.googleapis.com/auth/drive"
            ]
        )
        return gspread.authorize(credentials)
    except Exception as e:
        st.error(f"⚠️ Erreur Auth : {e}")
        return None

# ==============================================================================
# 3. APPLICATION PRINCIPALE
# ==============================================================================
def run_vortex_system():
    # Configuration de la page
    st.set_page_config(
        page_title="Projet Data",
        page_icon="🔺",
        layout="wide",
        initial_sidebar_state="expanded"
    )
    
    # Initialisation du style
    apply_vortex_style()
    
    client = get_gspread_client()
    SHEET_NAME = "bdd_mvp_faker"  # Nom du fichier Google Sheets

    # --- TEST DE CONNEXION RÉEL ---
    is_connected = False
    connection_error = None

    if client:
        try:
            # On tente une ouverture légère pour vérifier l'accès
            client.open(SHEET_NAME) 
            is_connected = True
        except Exception as e:
            connection_error = e
            
    if client:
        # --- SIDEBAR ---
        st.sidebar.title("🔺 Projet Data")
        st.sidebar.caption("Système de prédiction")
        st.sidebar.divider()
        
        # Menu de Navigation
        menu = st.sidebar.radio(
            "Navigation", 
            [  "Dashboard", "AIP Terminal", "Forecasting", "Features", "Data Explorer", "ETL Control", "Indicateurs DQ", "Add Data" ] # <--- AJOUT
        )
        
        st.sidebar.divider()
        
        # Informations système
        st.sidebar.markdown("#### 📡 Statut Système")


        # LOGIQUE D'AFFICHAGE INTELLIGENTE
        if is_connected:
            st.sidebar.success("✅ Système En Ligne")
            st.sidebar.caption(f"Linked to: {SHEET_NAME}")
        else:
            st.sidebar.error("❌ Erreur de Liaison")
            if connection_error:
                # Si c'est une erreur 403 (Drive API) ou File Not Found
                if "403" in str(connection_error):
                    st.sidebar.warning("⚠️ API Drive désactivée ou fichier non partagé.")
                elif "Sprite" in str(connection_error) or "Not Found" in str(connection_error): # Gspread renvoie souvent SpriteSpreadsheetNotFound
                    st.sidebar.warning(f"⚠️ Fichier '{SHEET_NAME}' introuvable.")
                else:
                    st.sidebar.warning("⚠️ Erreur technique :")
                    # On affiche le vrai message d'erreur pour comprendre
                    st.sidebar.code(str(connection_error))

                
        st.sidebar.divider()
        
        if st.sidebar.button("🔄 Reset System", use_container_width=True):
            for key in list(st.session_state.keys()):
                del st.session_state[key]
            st.rerun()

        # --- LOGIQUE D'AFFICHAGE ---
        if menu == "Dashboard":
            if Dashboard:
                try:
                    Dashboard.show_dashboard(client, SHEET_NAME)
                except Exception as e:
                    st.error(f"💥 Erreur Dashboard : {e}")
                    st.exception(e)
            else:
                st.error("❌ Module Dashboard non chargé.")
                
        elif menu == "Data Explorer":
            # Pas de titre ici, on laisse le module gérer son affichage pour éviter les doublons
            
            # On vérifie que le module est chargé ET que la fonction existe
            if Data_Exploration and hasattr(Data_Exploration, 'show_terminal'):
                try:
                    # On passe le relais au module
                    Data_Exploration.show_terminal(client, SHEET_NAME)
                except Exception as e:
                    # Filet de sécurité en cas de crash INTERNE au module
                    st.error(f"💥 Erreur critique dans le module Data_Exploration : {e}")
                    st.exception(e) # Affiche la trace complète pour le débogage
            else:
                # Gestion explicite des erreurs d'import
                st.error("❌ Module Data_Exploration non disponible")
                if not Data_Exploration:
                    st.warning("Diagnostic : L'import initial a échoué (Variable = None).")
                else:
                    st.warning("Diagnostic : Le module est chargé mais la fonction 'show_terminal' est introuvable.")
        
        elif menu == "AIP Terminal":
            # --- DESIGN RECTANGULAIRE AIP (Style Palantir) ---
            st.markdown("""
                <style>
                .stAlert { 
                    border-radius: 0px !important; 
                    border: 1px solid #30363d !important; 
                    background-color: #0d1117 !important; 
                }
                .stAlert p { color: #e0e0e0 !important; }
                </style>
            """, unsafe_allow_html=True)

            # 1. Vérification : Le module est-il chargé ?
            if not AIP:
                st.error("❌ Le module AIP (Jasmine Agent) n'est pas chargé.")
                st.info("Vérifiez l'import en haut du fichier ou les dépendances (groq, google-generativeai).")
            
            # 2. Vérification : La fonction existe-t-elle ?
            else:
                if hasattr(AIP, "show_terminal"):
                    # Cas idéal : Fonction trouvée
                    AIP.show_terminal(client, SHEET_NAME)
                
                elif hasattr(AIP, "show_jasmine_interface"):
                    # Cas compatible : Ancien nom trouvé
                    AIP.show_jasmine_interface(client, SHEET_NAME)
                
                else:
                    # Cas erreur : Module là, mais fonction introuvable
                    st.error("❌ Erreur de point d'entrée dans AIP.py")
                    available_funcs = [f for f in dir(AIP) if not f.startswith('_')]
                    st.code(f"Fonctions disponibles détectées : {available_funcs}")
            
        elif menu == "Indicateurs DQ":
            if Indicateurs_DQ and hasattr(Indicateurs_DQ, 'show_dq_dashboard'):
                try:
                    # Mode Google Sheets (cohérent avec le reste de l'app)
                    Indicateurs_DQ.show_dq_dashboard(client, SHEET_NAME)
                except Exception as e:
                    st.error(f"💥 Erreur critique dans le module DQ : {e}")
                    st.exception(e)
            else:
                st.error("❌ Le module Indicateurs_DQ est introuvable ou mal chargé.")
                st.info("Vérifiez que le fichier `src/module/Indicateurs_DQ.py` existe bien.")



        elif menu == "Features":
            if Features:
                try:
                    Features.show_feature_engine(client, SHEET_NAME)
                except Exception as e:
                    st.error(f"💥 Erreur Features Engine : {e}")
                    st.exception(e)
            else:
                st.error("❌ Module Features non chargé.")


        elif menu == "Forecasting":
            if Forecasting:
                try:
                    Forecasting.show_forecasting_module(client, SHEET_NAME)
                except Exception as e:
                    st.error(f"💥 Erreur Forecasting : {e}")
                    st.exception(e)
            else:
                st.error("❌ Module Forecasting non chargé.")


        # --- NOUVEAU BLOC INJECTION ---
        elif menu == "Add Data":
            if add_data:
                try:
                    add_data.show_data_injector(client, SHEET_NAME)
                except Exception as e:
                    st.error(f"💥 Erreur Injecteur : {e}")
                    st.exception(e)
            else:
                st.error("❌ Module add_data non chargé.")

        elif menu == "ETL Control":

            # Vérification de l'import
            if Validation_Contract:
                # Instanciation du moteur
                etl_engine = Validation_Contract.ETL_Engine(client, SHEET_NAME)
                
                # --- AUTOMATISATION (AUTO-RUN) ---
                # On vérifie si les données manquent. Si oui, on lance l'ETL automatiquement.
                if 'data_explorer' not in st.session_state:
                    with st.spinner("🚀 Démarrage automatique du Pipeline ETL..."):
                        success, result = etl_engine.run_etl()
                        
                        if not success:
                            st.error(f"❌ Échec critique du pipeline : {result}")
                
                # --- AFFICHAGE DU RAPPORT ---
                # À ce stade, soit l'ETL vient de tourner, soit les données étaient déjà là.
                if 'data_explorer' in st.session_state:
                    explorer = st.session_state.data_explorer
                    
                    # Petit header de statut
                    col_status, col_btn = st.columns([3, 1])
                    with col_status:
                        st.success(f"✅ Données synchronisées (Dernière MAJ : {explorer.load_timestamp.strftime('%H:%M:%S')})")
                    with col_btn:
                        # Bouton optionnel pour forcer la mise à jour si les données Google Sheets ont changé
                        if st.button("🔄 Forcer Rechargement"):
                            del st.session_state['data_explorer']
                            st.rerun()

                    # Affichage des graphiques de performance
                    explorer.show_transformation_report()
            
            else:
                st.error("❌ Le module Validation_Contract n'est pas chargé.")

    
    else:
        st.error("❌ Impossible de se connecter à Google Sheets")
        st.info("""
        **Configuration requise :**
        1. Créez un fichier `.streamlit/secrets.toml`
        2. Ajoutez votre clé JSON Google Sheets :
        ```toml
        GSHEETS_JSON = '''
        {
          "type": "service_account",
          "project_id": "...",
          ...
        }
        '''
        ```
        """)

# ==============================================================================
# 4. POINT D'ENTRÉE STREAMLIT
# ==============================================================================
if __name__ == "__main__":
    run_vortex_system()