jaimalklyde commited on
Commit
e541241
·
verified ·
1 Parent(s): 26dd054

Update src/module/AIP.py

Browse files
Files changed (1) hide show
  1. src/module/AIP.py +83 -128
src/module/AIP.py CHANGED
@@ -1,14 +1,9 @@
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
@@ -84,37 +79,16 @@ class JasmineAgent:
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:
@@ -128,12 +102,9 @@ class JasmineAgent:
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)}}
@@ -142,45 +113,29 @@ class JasmineAgent:
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
 
@@ -195,6 +150,9 @@ class JasmineAgent:
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}
@@ -208,54 +166,70 @@ class JasmineAgent:
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
- """
256
 
 
 
 
257
  def ask(self, user_message, chat_history=[]):
258
- system_prompt = self.build_system_prompt()
 
 
 
 
 
 
 
 
 
259
  response_payload = None
260
  used_model = None
261
 
@@ -279,37 +253,18 @@ class JasmineAgent:
279
  try:
280
  decision = json.loads(re.sub(r'```json\s*|\s*```', '', response_payload).strip())
281
  tool_name = decision.get("tool")
282
- thought = f"Modèle: {used_model}\n\n{decision.get('thought', '')}"
283
 
284
  if tool_name in self.tools_map:
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"):
@@ -326,7 +281,7 @@ def show_terminal(client, sheet_name="bdd_mvp_faker"):
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()
 
1
  """
2
+ MODULE JASMINE AGENT - VERSION ULTIME (FULL STACK + EMPATHIE)
3
+ =============================================================
4
+ Responsabilité : Agent IA centralisé.
5
+ Capacités : ETL, Dashboard, Data Lab, DQ, Features, Forecasting.
6
+ Ton : Professionnel, Proactif et Humain.
 
 
 
 
 
7
  """
8
 
9
  import google.generativeai as genai
 
79
 
80
  # --- CARTE DES OUTILS ---
81
  self.tools_map = {
82
+ "run_etl_pipeline": {"func": self._run_etl_wrapper, "desc": "1. ETL : Nettoyage et mise à jour des données."},
83
+ "run_feature_engineering": {"func": self._run_features_wrapper, "desc": "2. ML Prep : Transformation des données pour l'IA."},
84
+ "train_forecasting_models": {"func": self._train_models_wrapper, "desc": "3. Training : Entraînement des modèles prédictifs."},
85
+ "get_future_forecast": {"func": self._get_forecast_wrapper, "desc": "4. Prédiction : Obtenir les prévisions futures."},
86
+ "get_hospital_kpis": {"func": self._get_kpis_wrapper, "desc": "5. Dashboard : KPIs actuels (Lits, RH, Stocks)."},
87
+ "analyze_patient_stats": {"func": self._get_data_exploration_wrapper, "desc": "6. Data Lab : Stats patients passées."},
88
+ "get_data_quality_report": {"func": self._get_dq_stats_wrapper, "desc": "7. Audit : Qualité et fiabilité des données."}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  }
90
 
91
+ # --- WRAPPERS TECHNIQUES ---
92
 
93
  def _run_etl_wrapper(self, args=None):
94
  try:
 
102
 
103
  def _run_features_wrapper(self, args=None):
104
  try:
105
+ if 'data_explorer' not in st.session_state: return {"status": "erreur", "message": "Lancez l'ETL d'abord."}
 
 
106
  engineer = FeatureEngineer(st.session_state.data_explorer)
107
  df_final, msg = engineer.build_full_dataset()
 
108
  if df_final is not None:
109
  st.session_state['ml_features'] = df_final
110
  return {"status": "succès", "details": {"lignes": len(df_final), "features": len(df_final.columns)}}
 
113
 
114
  def _train_models_wrapper(self, args=None):
115
  try:
116
+ if 'ml_features' not in st.session_state: return {"status": "erreur", "message": "Features manquantes."}
 
 
117
  forecaster = HospitalForecaster(st.session_state['ml_features'])
118
+ forecaster.prepare_data(target_col='nb_admissions')
119
  metrics = forecaster.train_all_models()
 
120
  st.session_state['forecaster'] = forecaster
121
  st.session_state['metrics'] = metrics
122
+ best = forecaster.best_model_name
123
+ return {"status": "succès", "details": {"best_model": best, "r2": metrics[best]['R²'], "mae": metrics[best]['MAE']}}
 
124
  except Exception as e: return {"status": "crash", "message": str(e)}
125
 
126
  def _get_forecast_wrapper(self, args=None):
127
  try:
128
+ if 'forecaster' not in st.session_state: return {"status": "erreur", "message": "Modèles non entraînés."}
 
 
129
  forecaster = st.session_state['forecaster']
 
130
  dates, values, _ = forecaster.generate_future_forecast_recursive(months=3)
131
  alerts = forecaster.detect_future_anomalies(values)
132
+ return {"status": "succès", "details": {"tendance": "Hausse" if values[-1] > values[0] else "Baisse", "max_prevu": float(np.max(values)), "alerte_count": len(alerts), "prochaine_alerte": alerts[0] if alerts else None}}
 
 
 
 
 
 
 
 
 
133
  except Exception as e: return {"status": "crash", "message": str(e)}
134
 
135
  def _get_kpis_wrapper(self, args=None):
136
  try:
137
  dash = HospitalDashboard(self.gspread_client, "bdd_mvp_faker")
138
+ if not dash.load_data(): return {"status": "erreur", "message": "Données Dashboard indisponibles."}
139
  return {"status": "succès", "details": dash._get_kpis()}
140
  except Exception as e: return {"status": "crash", "message": str(e)}
141
 
 
150
  df_pat['age'] = pd.to_numeric(df_pat['age'], errors='coerce')
151
  stats['age_moyen'] = round(df_pat['age'].mean(), 1)
152
  stats['total_patients'] = len(df_pat)
153
+ counts = df_pat['sexe'].value_counts(normalize=True)
154
+ stats['ratio_hommes'] = round(counts.get('M', 0) * 100, 1)
155
+ stats['ratio_femmes'] = round(counts.get('F', 0) * 100, 1)
156
  if df_sej is not None:
157
  stats['pathologie_top'] = df_sej['pathologie_code'].mode()[0]
158
  return {"status": "succès", "details": stats}
 
166
  return {"status": "succès", "details": {"score": scores['global'], "anomalies": dq.metrics['patients']['anomalies']}}
167
  except Exception as e: return {"status": "crash", "message": str(e)}
168
 
169
+ # --- HUMANISATION (Le Moteur de Personnalité) ---
170
+ def _generate_human_response(self, tool_name, d):
171
+ """Traduit les données JSON en langage naturel professionnel"""
 
172
 
173
  if tool_name == "get_hospital_kpis":
174
+ taux = float(d.get('taux_global', 0))
175
+ rh = max(0, d.get('tension_rh', 0))
176
+ ruptures = int(d.get('nb_ruptures', 0))
177
+
178
+ msg = "Voici la situation actuelle de l'établissement :\n\n"
179
+ if taux > 95: msg += f"🔴 **URGENCE :** Saturation critique (**{taux:.1f}%**). Plan Blanc recommandé.\n"
180
+ elif taux > 85: msg += f"🟠 **Tension :** L'occupation est élevée (**{taux:.1f}%**).\n"
181
+ else: msg += f"🟢 **Lits :** Situation calme (**{taux:.1f}%** d'occupation).\n"
182
+
183
+ if rh > 20: msg += f"⚠️ **RH :** Tension forte sur le personnel (**{rh:.1f}%** manquant).\n"
184
+ else: msg += f"✅ **RH :** Effectifs stables.\n"
185
+
186
+ if ruptures > 0: msg += f"📦 **Logistique :** {ruptures} rupture(s) de stock détectée(s)."
187
+ else: msg += "📦 **Logistique :** Stocks au vert."
188
+ return msg
189
 
190
  elif tool_name == "get_future_forecast":
191
+ tendance = d['tendance']
192
+ max_p = d['max_prevu']
193
+ alerts = d.get('alerte_count', 0)
194
+ msg = f"Selon mes modèles, la tendance à 3 mois est à la **{tendance}**.\n"
195
+ msg += f"Le pic d'admissions prévu est de **{max_p:.0f} patients/jour**.\n\n"
196
+ if alerts > 0:
197
+ next_alert = d['prochaine_alerte']
198
+ msg += f"⚠️ **Vigilance :** Un pic anormal est détecté autour de **J+{next_alert['day']}**."
199
+ else: msg += "✅ Aucune anomalie majeure prévue."
200
+ return msg
201
 
202
  elif tool_name == "train_forecasting_models":
203
+ return f"Entraînement terminé. Le modèle **{d['best_model']}** est le plus performant (Fiabilité : **{d['r2']*100:.1f}%**)."
 
 
204
 
205
+ elif tool_name == "run_etl_pipeline":
206
+ return f"Mise à jour effectuée. La base contient maintenant **{d['total_rows_clean']:,}** lignes consolidées."
207
 
208
+ elif tool_name == "run_feature_engineering":
209
+ return f"Préparation ML terminée : **{d['lignes']:,} vecteurs** générés avec **{d['features']} variables** d'analyse."
210
 
211
+ elif tool_name == "analyze_patient_stats":
212
+ return f"Analyse sur **{d['total_patients']} patients**. Âge moyen : **{d['age_moyen']} ans**. Pathologie dominante : **{d['pathologie_top']}**."
213
+
214
+ elif tool_name == "get_data_quality_report":
215
+ score = d['score']
216
+ if score > 85: return f"🛡️ Données fiables (**{score}/100**)."
217
+ return f"⚠️ **Attention Data :** Score de qualité faible (**{score}/100**) avec **{d['anomalies']} anomalies**."
 
 
 
 
 
 
 
 
 
 
218
 
219
+ return "Opération réussie."
220
+
221
+ # --- INTELLIGENCE PRINCIPALE ---
222
  def ask(self, user_message, chat_history=[]):
223
+ system_prompt = f"""
224
+ ROLE: Tu es Jasmine, Directrice adjointe virtuelle.
225
+ TON: Professionnel, concis, proactif.
226
+ OUTILS:
227
+ {chr(10).join([f"- {k}: {v['desc']}" for k, v in self.tools_map.items()])}
228
+ RÈGLES:
229
+ 1. Utilise les outils pour répondre aux faits.
230
+ 2. Réponds TOUJOURS en JSON : {{ "thought": "...", "tool": "...", "args": {{}}, "answer": "..." }}
231
+ """
232
+
233
  response_payload = None
234
  used_model = None
235
 
 
253
  try:
254
  decision = json.loads(re.sub(r'```json\s*|\s*```', '', response_payload).strip())
255
  tool_name = decision.get("tool")
256
+ thought = f"🧠 Modèle: {used_model}\n💭 {decision.get('thought', '')}"
257
 
258
  if tool_name in self.tools_map:
259
  res = self.tools_map[tool_name]["func"]({})
260
  if res["status"] == "succès":
261
+ # UTILISATION DE LA NOUVELLE MÉTHODE HUMAINE
262
+ final_response = self._generate_human_response(tool_name, res["details"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
263
  else:
264
+ final_response = f"⚠️ Problème technique : {res.get('message')}"
265
  return final_response, thought, res
266
 
267
+ return decision.get("answer", "Je vous écoute."), thought, None
268
  except Exception as e: return f"Erreur système : {str(e)}", None, None
269
 
270
  def show_terminal(client, sheet_name="bdd_mvp_faker"):
 
281
  if msg.get("data"):
282
  with st.expander("💾 SYSTEM PAYLOAD"): st.json(msg["data"])
283
 
284
+ user_input = st.chat_input("Ex: 'Analyse les stocks' ou 'Lance la prédiction'...")
285
  if user_input:
286
  st.session_state.chat_history.append({"role": "user", "content": user_input})
287
  st.rerun()