ProjetData / src /streamlit_app.py
jaimalklyde's picture
Update src/streamlit_app.py
c605521 verified
Raw History Blame Contribute Delete
17.1 kB
# ==============================================================================
# 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()