jaimalklyde commited on
Commit
607e067
·
verified ·
1 Parent(s): 286b679

Update src/module/AIP.py

Browse files
Files changed (1) hide show
  1. src/module/AIP.py +150 -76
src/module/AIP.py CHANGED
@@ -1,9 +1,14 @@
1
  """
2
- MODULE JASMINE AGENT - VERSION OMNISCIENTE & PROACTIVE
3
- ======================================================
4
- Responsabilité : Agent IA centralisé.
5
- Capacités : ETL, Dashboard, Data Lab, Data Quality.
6
- Ajout : Moteur de diagnostic proactif (Seuils d'alerte & Recommandations).
 
 
 
 
 
7
  """
8
 
9
  import google.generativeai as genai
@@ -16,16 +21,18 @@ import pandas as pd
16
  import numpy as np
17
  import traceback
18
 
19
- # --- IMPORT DES MODULES ---
20
  try:
21
  from module.Validation_Contract import ETL_Engine
22
  from module.Dashboard import HospitalDashboard
23
  from module.Data_Exploration import HospitalAdvancedAnalyzer
24
  from module.Indicateurs_DQ import HospitalDataQualityAnalyzer, load_from_google_sheets
 
 
25
  except ImportError as e:
26
- st.error(f"❌ Erreur d'importation des modules : {str(e)}")
27
 
28
- # --- STYLE GOTHAM (INCHANGÉ) ---
29
  def apply_custom_styles():
30
  st.markdown("""
31
  <style>
@@ -57,6 +64,7 @@ class JasmineAgent:
57
  self.google_key = os.environ.get("GOOGLE_API_KEY")
58
  self.groq_key = os.environ.get("GROQ_API_KEY")
59
 
 
60
  if not self.google_key:
61
  try: self.google_key = st.secrets.get("GOOGLE_API_KEY")
62
  except: pass
@@ -74,14 +82,40 @@ class JasmineAgent:
74
  "llama-3.1-8b-instant"
75
  ]
76
 
 
77
  self.tools_map = {
78
- "run_etl_pipeline": {"func": self._run_etl_wrapper, "desc": "Mise à jour et nettoyage."},
79
- "get_hospital_kpis": {"func": self._get_kpis_wrapper, "desc": "KPIs (Lits, RH, Stocks)."},
80
- "analyze_patient_stats": {"func": self._get_data_exploration_wrapper, "desc": "Data Lab (Âge, Pathos)."},
81
- "get_data_quality_report": {"func": self._get_dq_stats_wrapper, "desc": "Audit Qualité/Erreurs."}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  }
83
 
84
- # --- WRAPPERS TECHNIQUES (INCHANGÉS) ---
 
85
  def _run_etl_wrapper(self, args=None):
86
  try:
87
  engine = ETL_Engine(self.gspread_client, sheet_name="bdd_mvp_faker")
@@ -89,13 +123,64 @@ class JasmineAgent:
89
  if success:
90
  st.session_state.data_explorer = result
91
  return {"status": "succès", "details": result.get_summary()}
92
- return {"status": "erreur", "message": "Échec de l'exécution ETL."}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  except Exception as e: return {"status": "crash", "message": str(e)}
94
 
95
  def _get_kpis_wrapper(self, args=None):
96
  try:
97
  dash = HospitalDashboard(self.gspread_client, "bdd_mvp_faker")
98
- if not dash.load_data(): return {"status": "erreur", "message": "Données Dashboard indisponibles."}
99
  return {"status": "succès", "details": dash._get_kpis()}
100
  except Exception as e: return {"status": "crash", "message": str(e)}
101
 
@@ -106,14 +191,11 @@ class JasmineAgent:
106
  df_pat = analyzer.get_df('patients')
107
  df_sej = analyzer.get_df('sejours')
108
  stats = {}
109
- if df_pat is not None and not df_pat.empty:
110
  df_pat['age'] = pd.to_numeric(df_pat['age'], errors='coerce')
111
  stats['age_moyen'] = round(df_pat['age'].mean(), 1)
112
  stats['total_patients'] = len(df_pat)
113
- counts = df_pat['sexe'].value_counts(normalize=True)
114
- stats['ratio_hommes'] = round(counts.get('M', 0) * 100, 1)
115
- stats['ratio_femmes'] = round(counts.get('F', 0) * 100, 1)
116
- if df_sej is not None and not df_sej.empty:
117
  stats['pathologie_top'] = df_sej['pathologie_code'].mode()[0]
118
  return {"status": "succès", "details": stats}
119
  except Exception as e: return {"status": "crash", "message": str(e)}
@@ -121,61 +203,53 @@ class JasmineAgent:
121
  def _get_dq_stats_wrapper(self, args=None):
122
  try:
123
  raw_data = load_from_google_sheets(self.gspread_client, "bdd_mvp_faker")
124
- dq_analyzer = HospitalDataQualityAnalyzer(raw_data)
125
- scores = dq_analyzer.compute_global_score()
126
- metrics = dq_analyzer.metrics
127
- return {"status": "succès", "details": {"score": scores['global'], "completude": scores['completeness'], "anomalies": metrics['patients']['anomalies']}}
128
  except Exception as e: return {"status": "crash", "message": str(e)}
129
 
130
- # --- MOTEUR DE DIAGNOSTIC PROACTIF (NOUVEAU) ---
131
  def _generate_diagnostic(self, tool_name, data):
132
- """Analyse les données pour générer des alertes et conseils automatiques"""
133
  alerts = []
134
 
135
  if tool_name == "get_hospital_kpis":
136
- # 1. Occupation
137
  taux = float(data.get('taux_global', 0))
138
- if taux >= 95:
139
- alerts.append("🔴 **ALERTE CRITIQUE :** Saturation imminente (>95%). Activez le Plan Blanc et délestez les urgences.")
140
- elif taux >= 85:
141
- alerts.append("🟠 **ATTENTION :** Tensions fortes (>85%). Préparez des lits d'aval.")
142
-
143
- # 2. Stocks
144
- ruptures = int(data.get('nb_ruptures', 0))
145
- if ruptures > 0:
146
- alerts.append(f"📦 **LOGISTIQUE :** {ruptures} matériels en rupture critique. Vérifiez les commandes urgentes.")
147
-
148
- # 3. RH (Correction visuelle des valeurs négatives si nécessaire)
149
- tension_rh = float(data.get('tension_rh', 0))
150
- if tension_rh > 20: # Seuil arbitraire de tension
151
- alerts.append("🩺 **PERSONNEL :** Manque d'effectif critique. Rappel de personnel nécessaire.")
152
 
153
- elif tool_name == "get_data_quality_report":
154
- score = float(data.get('score', 0))
155
- if score < 80:
156
- alerts.append("📉 **FIABILITÉ :** Score de qualité faible (<80%). Les KPIs financiers risquent d'être faussés.")
157
- if int(data.get('anomalies', 0)) > 100:
158
- alerts.append("👉 **ACTION :** Nettoyer la base Patients (nombreux doublons/erreurs détectés).")
159
 
160
- if alerts:
161
- return "\n\n" + "\n".join(alerts)
 
 
 
 
162
  return ""
163
 
164
- # --- CORE LOGIC ---
165
 
166
  def build_system_prompt(self):
167
  tools_desc = "\n".join([f"- {k}: {v['desc']}" for k, v in self.tools_map.items()])
168
  return f"""
169
- ROLE: Tu es Jasmine, l'IA de supervision.
170
- OUTILS DISPONIBLES :
171
  {tools_desc}
172
  RÈGLES :
173
- 1. Utilise les outils pour répondre aux faits.
174
- 2. Réponds TOUJOURS au format JSON :
 
175
  {{
176
- "thought": "Ton analyse...",
177
  "tool": "nom_outil" (ou "none"),
178
- "answer": "Ta réponse directe si tool=none",
179
  "args": {{}}
180
  }}
181
  """
@@ -200,7 +274,7 @@ class JasmineAgent:
200
  break
201
  except: continue
202
 
203
- if not response_payload: return "❌ Erreur de connexion API.", None, None
204
 
205
  try:
206
  decision = json.loads(re.sub(r'```json\s*|\s*```', '', response_payload).strip())
@@ -211,32 +285,32 @@ class JasmineAgent:
211
  res = self.tools_map[tool_name]["func"]({})
212
  if res["status"] == "succès":
213
  d = res["details"]
214
-
215
- # Génération du Diagnostic Proactif
216
  diagnostic = self._generate_diagnostic(tool_name, d)
217
-
218
- # Affichage des Résultats
219
  if tool_name == "run_etl_pipeline":
220
- final_response = f"✅ **ETL Terminé.**\n🔹 Tables : {d['tables_loaded']}\n🔹 Lignes : {d['total_rows_clean']:,}"
 
 
 
 
 
 
221
  elif tool_name == "get_hospital_kpis":
222
- # Protection affichage RH (max pour éviter les -1700%)
223
- rh_display = max(0, d['tension_rh'])
224
- final_response = f"📊 **KPIs Direction :**\n🔹 Occupation : {d['taux_global']:.1f}%\n🔹 Tension RH : {rh_display:.1f}%\n🔹 Admissions : {d['nb_adm']}\n🔹 Ruptures : {d['nb_ruptures']}"
225
  elif tool_name == "analyze_patient_stats":
226
- final_response = f"🔬 **Data Lab :**\n🔹 Pop : {d['total_patients']}\n🔹 Âge moy : {d['age_moyen']} ans\n🔹 H {d['ratio_hommes']}% | F {d['ratio_femmes']}%\n🔹 Top Patho : {d['pathologie_top']}"
227
  elif tool_name == "get_data_quality_report":
228
- icon = "🟢" if d['score'] > 90 else "🔴"
229
- final_response = f"🛡️ **Qualité :** {icon} {d['score']}/100\n🔹 Complétude : {d['completude']}%\n🔹 Anomalies : {d['anomalies']}"
230
 
231
- # Injection du diagnostic s'il existe
232
- if diagnostic:
233
- final_response += diagnostic
234
-
235
- else: final_response = f"⚠️ {res.get('message')}"
236
  return final_response, thought, res
237
 
238
- return decision.get("answer", "Analyse effectuée."), thought, None
239
- except Exception as e: return f"Erreur décodage: {str(e)}", None, None
240
 
241
  def show_terminal(client, sheet_name="bdd_mvp_faker"):
242
  apply_custom_styles()
@@ -252,7 +326,7 @@ def show_terminal(client, sheet_name="bdd_mvp_faker"):
252
  if msg.get("data"):
253
  with st.expander("💾 SYSTEM PAYLOAD"): st.json(msg["data"])
254
 
255
- user_input = st.chat_input("Ex: 'Audit Qualité' ou 'État des Urgences'...")
256
  if user_input:
257
  st.session_state.chat_history.append({"role": "user", "content": user_input})
258
  st.rerun()
 
1
  """
2
+ MODULE JASMINE AGENT - VERSION ULTIME (FULL STACK INTEGRATION)
3
+ ==============================================================
4
+ Responsabilité : Agent IA centralisé avec contrôle total sur la chaîne de données.
5
+ Capacités :
6
+ 1. ETL (Nettoyage)
7
+ 2. Dashboard (KPIs Présent)
8
+ 3. Data Lab (Exploration Passé)
9
+ 4. Data Quality (Audit Fiabilité)
10
+ 5. Feature Engineering (Préparation ML)
11
+ 6. Forecasting (Prédiction Futur & Alertes)
12
  """
13
 
14
  import google.generativeai as genai
 
21
  import numpy as np
22
  import traceback
23
 
24
+ # --- IMPORT DES 6 MODULES CIBLES ---
25
  try:
26
  from module.Validation_Contract import ETL_Engine
27
  from module.Dashboard import HospitalDashboard
28
  from module.Data_Exploration import HospitalAdvancedAnalyzer
29
  from module.Indicateurs_DQ import HospitalDataQualityAnalyzer, load_from_google_sheets
30
+ from module.Features import FeatureEngineer
31
+ from module.Forecasting import HospitalForecaster
32
  except ImportError as e:
33
+ st.error(f"❌ Erreur critique d'importation : {str(e)}")
34
 
35
+ # --- STYLE GOTHAM ---
36
  def apply_custom_styles():
37
  st.markdown("""
38
  <style>
 
64
  self.google_key = os.environ.get("GOOGLE_API_KEY")
65
  self.groq_key = os.environ.get("GROQ_API_KEY")
66
 
67
+ # Fallbacks
68
  if not self.google_key:
69
  try: self.google_key = st.secrets.get("GOOGLE_API_KEY")
70
  except: pass
 
82
  "llama-3.1-8b-instant"
83
  ]
84
 
85
+ # --- CARTE DES OUTILS ---
86
  self.tools_map = {
87
+ "run_etl_pipeline": {
88
+ "func": self._run_etl_wrapper,
89
+ "desc": "1. ETL : Nettoyage et mise à jour des données."
90
+ },
91
+ "run_feature_engineering": {
92
+ "func": self._run_features_wrapper,
93
+ "desc": "2. ML Prep : Transformation des données pour l'IA (Lags, Rolling)."
94
+ },
95
+ "train_forecasting_models": {
96
+ "func": self._train_models_wrapper,
97
+ "desc": "3. Training : Entraînement des modèles prédictifs (XGBoost/LightGBM)."
98
+ },
99
+ "get_future_forecast": {
100
+ "func": self._get_forecast_wrapper,
101
+ "desc": "4. Prédiction : Obtenir les prévisions futures et les alertes de saturation."
102
+ },
103
+ "get_hospital_kpis": {
104
+ "func": self._get_kpis_wrapper,
105
+ "desc": "5. Dashboard : KPIs actuels (Lits, RH, Stocks)."
106
+ },
107
+ "analyze_patient_stats": {
108
+ "func": self._get_data_exploration_wrapper,
109
+ "desc": "6. Data Lab : Stats patients passées."
110
+ },
111
+ "get_data_quality_report": {
112
+ "func": self._get_dq_stats_wrapper,
113
+ "desc": "7. Audit : Qualité et fiabilité des données."
114
+ }
115
  }
116
 
117
+ # --- WRAPPERS TECHNIQUES (LES BRAS DE L'IA) ---
118
+
119
  def _run_etl_wrapper(self, args=None):
120
  try:
121
  engine = ETL_Engine(self.gspread_client, sheet_name="bdd_mvp_faker")
 
123
  if success:
124
  st.session_state.data_explorer = result
125
  return {"status": "succès", "details": result.get_summary()}
126
+ return {"status": "erreur", "message": "Échec ETL."}
127
+ except Exception as e: return {"status": "crash", "message": str(e)}
128
+
129
+ def _run_features_wrapper(self, args=None):
130
+ try:
131
+ if 'data_explorer' not in st.session_state:
132
+ return {"status": "erreur", "message": "Lancez l'ETL d'abord."}
133
+
134
+ engineer = FeatureEngineer(st.session_state.data_explorer)
135
+ df_final, msg = engineer.build_full_dataset()
136
+
137
+ if df_final is not None:
138
+ st.session_state['ml_features'] = df_final
139
+ return {"status": "succès", "details": {"lignes": len(df_final), "features": len(df_final.columns)}}
140
+ return {"status": "erreur", "message": msg}
141
+ except Exception as e: return {"status": "crash", "message": str(e)}
142
+
143
+ def _train_models_wrapper(self, args=None):
144
+ try:
145
+ if 'ml_features' not in st.session_state:
146
+ return {"status": "erreur", "message": "Aucune feature ML. Lancez 'run_feature_engineering'."}
147
+
148
+ forecaster = HospitalForecaster(st.session_state['ml_features'])
149
+ forecaster.prepare_data(target_col='nb_admissions') # Cible par défaut
150
+ metrics = forecaster.train_all_models()
151
+
152
+ st.session_state['forecaster'] = forecaster
153
+ st.session_state['metrics'] = metrics
154
+
155
+ best_model = forecaster.best_model_name
156
+ return {"status": "succès", "details": {"best_model": best_model, "r2": metrics[best_model]['R²'], "mae": metrics[best_model]['MAE']}}
157
+ except Exception as e: return {"status": "crash", "message": str(e)}
158
+
159
+ def _get_forecast_wrapper(self, args=None):
160
+ try:
161
+ if 'forecaster' not in st.session_state:
162
+ return {"status": "erreur", "message": "Modèles non entraînés. Lancez 'train_forecasting_models'."}
163
+
164
+ forecaster = st.session_state['forecaster']
165
+ # Prédiction sur 3 mois
166
+ dates, values, _ = forecaster.generate_future_forecast_recursive(months=3)
167
+ alerts = forecaster.detect_future_anomalies(values)
168
+
169
+ return {
170
+ "status": "succès",
171
+ "details": {
172
+ "tendance": "Hausse" if values[-1] > values[0] else "Baisse",
173
+ "max_prevu": float(np.max(values)),
174
+ "alerte_count": len(alerts),
175
+ "prochaine_alerte": alerts[0] if alerts else None
176
+ }
177
+ }
178
  except Exception as e: return {"status": "crash", "message": str(e)}
179
 
180
  def _get_kpis_wrapper(self, args=None):
181
  try:
182
  dash = HospitalDashboard(self.gspread_client, "bdd_mvp_faker")
183
+ if not dash.load_data(): return {"status": "erreur", "message": "Data non chargées."}
184
  return {"status": "succès", "details": dash._get_kpis()}
185
  except Exception as e: return {"status": "crash", "message": str(e)}
186
 
 
191
  df_pat = analyzer.get_df('patients')
192
  df_sej = analyzer.get_df('sejours')
193
  stats = {}
194
+ if df_pat is not None:
195
  df_pat['age'] = pd.to_numeric(df_pat['age'], errors='coerce')
196
  stats['age_moyen'] = round(df_pat['age'].mean(), 1)
197
  stats['total_patients'] = len(df_pat)
198
+ if df_sej is not None:
 
 
 
199
  stats['pathologie_top'] = df_sej['pathologie_code'].mode()[0]
200
  return {"status": "succès", "details": stats}
201
  except Exception as e: return {"status": "crash", "message": str(e)}
 
203
  def _get_dq_stats_wrapper(self, args=None):
204
  try:
205
  raw_data = load_from_google_sheets(self.gspread_client, "bdd_mvp_faker")
206
+ dq = HospitalDataQualityAnalyzer(raw_data)
207
+ scores = dq.compute_global_score()
208
+ return {"status": "succès", "details": {"score": scores['global'], "anomalies": dq.metrics['patients']['anomalies']}}
 
209
  except Exception as e: return {"status": "crash", "message": str(e)}
210
 
211
+ # --- MOTEUR DE DIAGNOSTIC ---
212
  def _generate_diagnostic(self, tool_name, data):
213
+ """Génère des insights basés sur les résultats"""
214
  alerts = []
215
 
216
  if tool_name == "get_hospital_kpis":
 
217
  taux = float(data.get('taux_global', 0))
218
+ if taux >= 95: alerts.append("🔴 **CRITIQUE :** Saturation > 95%. Plan Blanc recommandé.")
219
+ elif taux >= 85: alerts.append("🟠 **ATTENTION :** Tensions fortes sur les lits.")
220
+ if int(data.get('nb_ruptures', 0)) > 0: alerts.append("📦 **STOCK :** Ruptures détectées.")
 
 
 
 
 
 
 
 
 
 
 
221
 
222
+ elif tool_name == "get_future_forecast":
223
+ if data.get('alerte_count', 0) > 0:
224
+ next_alert = data['prochaine_alerte']
225
+ alerts.append(f"🔮 **PRÉVISION :** Pic d'activité anormal prévu à J+{next_alert['day']} (Valeur: {next_alert['value']:.0f}).")
226
+ if data.get('tendance') == "Hausse":
227
+ alerts.append("📈 **TENDANCE :** Les admissions sont sur une pente ascendante pour le trimestre à venir.")
228
 
229
+ elif tool_name == "train_forecasting_models":
230
+ r2 = data.get('r2', 0)
231
+ if r2 > 0.8: alerts.append("✅ **IA PERFORMANCE :** Le modèle est très fiable (>80%).")
232
+ else: alerts.append("⚠️ **IA PERFORMANCE :** Fiabilité moyenne, prudence sur les prédictions.")
233
+
234
+ if alerts: return "\n\n" + "\n".join(alerts)
235
  return ""
236
 
237
+ # --- LOGIQUE PRINCIPALE ---
238
 
239
  def build_system_prompt(self):
240
  tools_desc = "\n".join([f"- {k}: {v['desc']}" for k, v in self.tools_map.items()])
241
  return f"""
242
+ ROLE: Tu es Jasmine, l'IA de supervision Hospitalière.
243
+ OUTILS :
244
  {tools_desc}
245
  RÈGLES :
246
+ 1. Utilise 'get_future_forecast' pour les questions sur l'avenir.
247
+ 2. Utilise 'train_forecasting_models' si l'utilisateur veut mettre à jour l'IA.
248
+ 3. Réponds TOUJOURS en JSON :
249
  {{
250
+ "thought": "Raisonnement...",
251
  "tool": "nom_outil" (ou "none"),
252
+ "answer": "Réponse directe si tool=none",
253
  "args": {{}}
254
  }}
255
  """
 
274
  break
275
  except: continue
276
 
277
+ if not response_payload: return "❌ Erreur API.", None, None
278
 
279
  try:
280
  decision = json.loads(re.sub(r'```json\s*|\s*```', '', response_payload).strip())
 
285
  res = self.tools_map[tool_name]["func"]({})
286
  if res["status"] == "succès":
287
  d = res["details"]
 
 
288
  diagnostic = self._generate_diagnostic(tool_name, d)
289
+
290
+ # --- AFFICHAGE DES RÉSULTATS ---
291
  if tool_name == "run_etl_pipeline":
292
+ final_response = f"✅ **ETL :** {d['tables_loaded']} tables chargées | {d['total_rows_clean']:,} lignes."
293
+ elif tool_name == "run_feature_engineering":
294
+ final_response = f"⚙️ **Features :** {d['lignes']:,} vecteurs créés avec {d['features']} variables."
295
+ elif tool_name == "train_forecasting_models":
296
+ final_response = f"🧠 **Training :** Champion = {d['best_model']} (R²: {d['r2']*100:.1f}% | Erreur moy: {d['mae']:.1f})."
297
+ elif tool_name == "get_future_forecast":
298
+ final_response = f"🔮 **Oracle :** Tendance {d['tendance']} | Max prévu: {d['max_prevu']:.0f} admissions."
299
  elif tool_name == "get_hospital_kpis":
300
+ rh = max(0, d['tension_rh'])
301
+ final_response = f"📊 **KPIs :** Lits {d['taux_global']:.1f}% | RH {rh:.1f}% | Stocks {d['nb_ruptures']} ruptures."
 
302
  elif tool_name == "analyze_patient_stats":
303
+ final_response = f"🔬 **Stats :** {d['total_patients']} patients | Âge {d['age_moyen']} ans | Top Patho: {d['pathologie_top']}."
304
  elif tool_name == "get_data_quality_report":
305
+ final_response = f"🛡️ **Qualité :** Score {d['score']}/100 | {d['anomalies']} anomalies détectées."
 
306
 
307
+ if diagnostic: final_response += diagnostic
308
+ else:
309
+ final_response = f"⚠️ **Erreur :** {res.get('message')}"
 
 
310
  return final_response, thought, res
311
 
312
+ return decision.get("answer", "Je suis à l'écoute."), thought, None
313
+ except Exception as e: return f"Erreur système : {str(e)}", None, None
314
 
315
  def show_terminal(client, sheet_name="bdd_mvp_faker"):
316
  apply_custom_styles()
 
326
  if msg.get("data"):
327
  with st.expander("💾 SYSTEM PAYLOAD"): st.json(msg["data"])
328
 
329
+ user_input = st.chat_input("Ex: 'Entraîne les modèles' ou 'Prévisions pour le mois prochain'...")
330
  if user_input:
331
  st.session_state.chat_history.append({"role": "user", "content": user_input})
332
  st.rerun()