jaimalklyde commited on
Commit
a5da6d0
·
verified ·
1 Parent(s): d736fc2

Update src/module/AIP.py

Browse files
Files changed (1) hide show
  1. src/module/AIP.py +122 -117
src/module/AIP.py CHANGED
@@ -1,9 +1,9 @@
1
  """
2
- MODULE JASMINE AGENT - EXECUTIVE EDITION
3
- ========================================
4
- Responsabilité : Cerveau central. Analyse stratégique et recommandations de direction.
5
  Niveau : C-Level (Directeur Général).
6
- Capacités : Connexion profonde aux moteurs de calcul (Forecasting, Dashboard, DQ).
7
  """
8
 
9
  import google.generativeai as genai
@@ -17,10 +17,8 @@ import numpy as np
17
  import traceback
18
 
19
  # --- IMPORTATION ROBUSTE DES MODULES ---
20
- # Jasmine s'adapte aux modules disponibles pour éviter les crashs
21
  try:
22
  from module.Validation_Contract import ETL_Engine
23
- # MISE A JOUR: Import des nouveaux moteurs du Dashboard
24
  from module.Dashboard import RealtimeMonitor, CrisisSimulator
25
  from module.Data_Exploration import HospitalAdvancedAnalyzer
26
  from module.Indicateurs_DQ import HospitalDataQualityAnalyzer, load_from_google_sheets
@@ -79,7 +77,7 @@ class JasmineAgent:
79
  "get_data_quality_report": {"func": self._get_dq_stats_wrapper, "desc": "Audit : Fiabilité des données."}
80
  }
81
 
82
- # --- WRAPPERS INTELLIGENTS (Extraction de contexte riche) ---
83
 
84
  def _run_etl_wrapper(self, args=None):
85
  try:
@@ -87,7 +85,6 @@ class JasmineAgent:
87
  success, result = engine.run_etl()
88
  if success:
89
  st.session_state.data_explorer = result
90
- # On retourne le résumé complet pour analyse
91
  return {"status": "succès", "details": result.get_summary()}
92
  return {"status": "erreur", "message": "Échec critique du pipeline ETL."}
93
  except Exception as e: return {"status": "crash", "message": str(e)}
@@ -99,10 +96,7 @@ class JasmineAgent:
99
  df_final, msg = engineer.build_full_dataset()
100
  if df_final is not None:
101
  st.session_state['ml_features'] = df_final
102
- # Analyse rapide des features générées pour le contexte
103
- cols = list(df_final.columns)
104
- lags = [c for c in cols if 'lag' in c]
105
- return {"status": "succès", "details": {"lignes": len(df_final), "total_features": len(cols), "lags_detectes": len(lags)}}
106
  return {"status": "erreur", "message": msg}
107
  except Exception as e: return {"status": "crash", "message": str(e)}
108
 
@@ -114,49 +108,46 @@ class JasmineAgent:
114
  metrics = forecaster.train_all_models()
115
  st.session_state['forecaster'] = forecaster
116
  st.session_state['metrics'] = metrics
 
117
 
118
- # On renvoie toutes les métriques pour comparaison
119
- return {"status": "succès", "details": metrics}
120
  except Exception as e: return {"status": "crash", "message": str(e)}
121
 
122
  def _get_forecast_wrapper(self, args=None):
123
  try:
124
  if 'forecaster' not in st.session_state: return {"status": "erreur", "message": "IA non entraînée."}
125
  forecaster = st.session_state['forecaster']
126
- # On demande 3 mois pour voir la tendance long terme
127
  dates, values, conf = forecaster.generate_future_forecast_recursive(months=3)
128
  alerts = forecaster.detect_future_anomalies(values)
129
-
130
- # Analyse statistique de la prédiction
131
  trend_pct = ((values[-1] - values[0]) / values[0]) * 100
132
 
133
  return {
134
  "status": "succès",
135
  "details": {
136
  "tendance_pct": trend_pct,
137
- "min_val": float(np.min(values)),
138
  "max_val": float(np.max(values)),
139
- "mean_val": float(np.mean(values)),
140
- "alertes": alerts, # Liste complète des alertes
141
- "confiance_spread": float(np.mean(conf['upper'] - conf['lower'])) # Indicateur d'incertitude
142
  }
143
  }
144
  except Exception as e: return {"status": "crash", "message": str(e)}
145
 
146
  def _get_kpis_wrapper(self, args=None):
147
  try:
148
- # CORRECTION MAJEURE : Utilisation du RealtimeMonitor
149
  if 'ml_features' not in st.session_state or 'forecaster' not in st.session_state:
150
  return {"status": "erreur", "message": "Le Cockpit nécessite les données ML et le modèle entraîné."}
151
 
152
  monitor = RealtimeMonitor(st.session_state['ml_features'], st.session_state['forecaster'])
153
- state = monitor.current_state # Récupère tout l'état (KPIs + tendances + alertes)
154
 
155
- # On ajoute l'analyse par service
156
  services_breakdown = monitor.get_service_breakdown()
157
- top_tension_service = services_breakdown.iloc[0].name if services_breakdown is not None else "Inconnu"
 
 
 
 
158
 
159
- # On fusionne tout
160
  state['top_tension_service'] = top_tension_service
161
 
162
  return {"status": "succès", "details": state}
@@ -168,14 +159,21 @@ class JasmineAgent:
168
  if not analyzer.load_data(): return {"status": "erreur", "message": "Données historiques inaccessibles."}
169
 
170
  df_pat = analyzer.get_df('patients')
 
171
  stats = {}
 
172
  if df_pat is not None:
173
  df_pat['age'] = pd.to_numeric(df_pat['age'], errors='coerce')
174
  stats['demographie'] = {
175
  'total': len(df_pat),
176
  'age_moyen': round(df_pat['age'].mean(), 1),
177
- 'senior_ratio': round((df_pat['age'] > 65).mean() * 100, 1)
 
178
  }
 
 
 
 
179
  return {"status": "succès", "details": stats}
180
  except Exception as e: return {"status": "crash", "message": str(e)}
181
 
@@ -184,9 +182,7 @@ class JasmineAgent:
184
  raw_data = load_from_google_sheets(self.gspread_client, "bdd_mvp_faker")
185
  dq = HospitalDataQualityAnalyzer(raw_data)
186
  scores = dq.compute_global_score()
187
-
188
- # On veut savoir où sont les problèmes exactement
189
- details_anomalies = {k: v['anomalies'] for k, v in dq.metrics.items() if 'anomalies' in v}
190
 
191
  return {
192
  "status": "succès",
@@ -197,140 +193,149 @@ class JasmineAgent:
197
  }
198
  except Exception as e: return {"status": "crash", "message": str(e)}
199
 
200
- # --- CERVEAU ANALYTIQUE (Human Response Engine) ---
201
  def _generate_human_response(self, tool_name, d):
202
  """
203
- Génère une analyse structurée : Constat -> Analyse -> Stratégie.
 
204
  """
205
 
206
- # 1. ANALYSE DU COCKPIT (Présent)
207
  if tool_name == "get_hospital_kpis":
208
  taux = d.get('taux_occupation', 0) * 100
209
  trend = d.get('trend_occupation', 0)
210
- score = d.get('health_score', 100)
211
  tension_rh = d.get('tension_personnel', 0)
212
  service_top = d.get('top_tension_service', 'N/A')
213
 
214
  msg = "### 🚁 Rapport de Situation Temps Réel\n\n"
 
 
 
215
 
216
- # Constat
217
- msg += f"**État Global :** Le score de santé de l'hôpital est de **{score:.0f}/100**.\n"
218
- msg += f"- Occupation : **{taux:.1f}%** ({trend:+.1f}% sur 72h)\n"
219
- msg += f"- Tension RH : **{tension_rh:.2f}** (Normal = 1.0)\n\n"
220
 
221
- # Analyse
222
- msg += "**🧠 Analyse Profonde :**\n"
223
- if taux > 90:
224
- msg += "Nous sommes en zone de saturation critique. La tendance montre une inertie qui risque de bloquer les urgences d'ici 24h. "
225
- elif taux > 80:
226
- msg += "L'activité est dense mais fluide. Les capacités tampons sont entamées. "
227
- else:
228
- msg += "L'hôpital est en sous-régime, ce qui permet d'absorber un éventuel choc externe. "
229
-
230
- if tension_rh > 1.1:
231
- msg += f"Le point de friction majeur est le service **{service_top}**, où le manque de personnel crée un goulot d'étranglement.\n\n"
232
- else:
233
- msg += "L'adéquation charge/ressource est optimale.\n\n"
234
-
235
- # Stratégie
236
- msg += "**🎯 Recommandations Stratégiques :**\n"
237
- if score < 60:
238
- msg += "1. **PRIORITAIRE :** Déclencher le niveau 1 du Plan Blanc pour libérer des lits.\n"
239
- msg += f"2. Redéployer immédiatement du personnel vers le service {service_top}.\n"
240
- msg += "3. Suspendre les admissions programmées non-urgentes pour 48h."
241
- elif score < 80:
242
- msg += f"1. Surveiller spécifiquement le service {service_top}.\n"
243
- msg += "2. Préparer l'ouverture de lits 'buffer' si la tendance hausse se confirme."
244
  else:
245
- msg += "1. Maintenir le dispositif actuel.\n2. Profiter de l'accalmie pour effectuer les maintenances logistiques."
246
-
247
  return msg
248
 
249
- # 2. ANALYSE PRÉDICTIVE (Futur)
250
  elif tool_name == "get_future_forecast":
251
  tendance = d['tendance_pct']
252
  max_val = d['max_val']
253
  alertes = d['alertes']
254
 
255
- msg = "### 🔮 Note de Prospective (3 Mois)\n\n"
256
-
257
- # Constat
258
  direction = "HAUSSE" if tendance > 0 else "BAISSE"
259
- msg += f"**Trajectoire :** Mes modèles anticipent une **{direction} globale de {abs(tendance):.1f}%** des admissions.\n"
260
- msg += f"**Point Haut :** Nous attendons un pic à **{max_val:.0f} patients/jour**.\n\n"
261
 
262
- # Analyse
263
- msg += "**🧠 Analyse des Risques :**\n"
264
  if len(alertes) > 0:
265
- nb_high = len([a for a in alertes if a['severity'] == 'HIGH'])
266
- first_alert_day = alertes[0]['day']
267
- msg += f"Je détecte **{len(alertes)} anomalies statistiques** dans la période à venir, dont **{nb_high} critiques**.\n"
268
- msg += f"La première tension majeure apparaîtra dans **{first_alert_day} jours**. Cela correspond probablement à une saisonnalité forte (ex: épidémie) ou à un événement cyclique mal anticipé.\n\n"
269
  else:
270
- msg += "La courbe prévisionnelle reste dans le tunnel de normalité. La variance est faible, ce qui indique une période de stabilité opérationnelle.\n\n"
271
 
272
- # Stratégie
273
- msg += "**🎯 Actions Préventives :**\n"
274
- if tendance > 15 or len(alertes) > 2:
275
- msg += "1. **Anticipation RH :** Valider les plannings de garde pour J+30 dès maintenant.\n"
276
- msg += "2. **Logistique :** Augmenter les stocks de consommables de +20% en prévision du pic.\n"
277
- msg += "3. **Flux :** Prévenir les services d'aval (SSR) qu'une vague d'entrée arrive."
278
  else:
279
- msg += "1. Optimiser le taux d'occupation des lits programmés.\n"
280
- msg += "2. Aucune mesure d'urgence requise."
281
-
282
  return msg
283
 
284
- # 3. QUALITÉ DES DONNÉES (Fondation)
285
  elif tool_name == "get_data_quality_report":
286
  scores = d['scores']
287
- anomalies = d['anomalies_par_table']
288
- total_anom = sum(anomalies.values())
289
 
290
- msg = "### 🛡️ Audit de Fiabilité des Données\n\n"
291
- msg += f"**Score de Confiance : {scores['global']}/100**\n"
292
- msg += f"(Complétude : {scores['completeness']:.1f}% | Exactitude : {scores['accuracy']:.1f}%)\n\n"
293
 
294
- msg += "**🧠 Diagnostic Technique :**\n"
295
  if total_anomalies > 0:
296
- msg += f"J'ai identifié un total de **{total_anom} lignes corrompues** qui 'bruitent' mes analyses.\n"
297
- worst_table = max(anomalies, key=anomalies.get)
298
- msg += f"Le point noir se situe dans la table **'{worst_table}'** ({anomalies[worst_table]} erreurs). "
299
- msg += "Ces erreurs (dates inversées, âges négatifs, doublons) réduisent la précision de mes prévisions d'environ 5 à 10%.\n\n"
300
  else:
301
- msg += "Le Data Lake est propre. Les signaux faibles sont clairs.\n\n"
302
-
303
- msg += "**🎯 Plan de Remédiation :**\n"
304
- if scores['global'] < 80:
305
- msg += "1. **URGENT :** Demander au DSI de purger la table 'Patients' (doublons).\n"
306
- msg += "2. Mettre en place des contraintes de saisie dans le logiciel métier (dates).\n"
307
- msg += "3. Relancer l'ETL après correction manuelle."
308
  else:
309
- msg += "Maintenir la surveillance automatisée hebdomadaire."
310
-
311
  return msg
312
 
313
- # Par défaut (autres outils)
314
- return f"✅ **Exécution Validée :** L'outil {tool_name} a fonctionné correctement. Les données ont été mises à jour dans le système central."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315
 
316
  # --- CERVEAU PRINCIPAL ---
317
  def ask(self, user_message, chat_history=[]):
318
  system_prompt = f"""
319
  ROLE: Tu es Jasmine, la Directrice des Opérations (COO) Virtuelle de l'hôpital.
320
- TON: Autoritaire mais bienveillant, précis, orienté solution. Tu ne dis pas "je pense", tu dis "l'analyse montre".
321
 
322
  OUTILS DISPONIBLES:
323
  {chr(10).join([f"- {k}: {v['desc']}" for k, v in self.tools_map.items()])}
324
 
325
- DIRECTIVE SUPRÊME:
326
- Ne te contente jamais de donner un chiffre. Donne le CONTEXTE, la CAUSE PROBABLE, et l'ACTION REQUISE.
 
 
 
327
 
328
- FORMAT DE RÉPONSE JSON ATTENDU:
329
  {{
330
- "thought": "Analyse interne de la demande...",
331
- "tool": "nom_de_l_outil_a_lancer",
332
  "args": {{}},
333
- "answer": "Texte si pas d'outil nécessaire"
334
  }}
335
  """
336
 
@@ -352,7 +357,7 @@ class JasmineAgent:
352
  break
353
  except: continue
354
 
355
- if not response_payload: return "❌ **Erreur Critique :** Mes services cognitifs ne répondent pas. Vérifiez les clés API.", None, None
356
 
357
  try:
358
  decision = json.loads(re.sub(r'```json\s*|\s*```', '', response_payload).strip())
@@ -362,7 +367,7 @@ class JasmineAgent:
362
  if tool_name in self.tools_map:
363
  res = self.tools_map[tool_name]["func"]({})
364
  if res["status"] == "succès":
365
- # C'est ici que la magie opère
366
  final_response = self._generate_human_response(tool_name, res["details"])
367
  else:
368
  final_response = f"⚠️ **Alerte Technique :** {res.get('message')}"
@@ -385,7 +390,7 @@ def show_terminal(client, sheet_name="bdd_mvp_faker"):
385
  if msg.get("data"):
386
  with st.expander("💾 SYSTEM PAYLOAD"): st.json(msg["data"])
387
 
388
- user_input = st.chat_input("Ordre à l'IA (ex: 'Audit complet', 'Situation RH', 'Prévisions')...")
389
  if user_input:
390
  st.session_state.chat_history.append({"role": "user", "content": user_input})
391
  st.rerun()
 
1
  """
2
+ MODULE JASMINE AGENT - EXECUTIVE DIRECTOR EDITION (FINAL)
3
+ =========================================================
4
+ Responsabilité : Cerveau central. Analyse stratégique, interprétation et recommandations.
5
  Niveau : C-Level (Directeur Général).
6
+ Correctifs appliqués : Gestion variable DQ, Clamping Scores, Analyse Démographique complète.
7
  """
8
 
9
  import google.generativeai as genai
 
17
  import traceback
18
 
19
  # --- IMPORTATION ROBUSTE DES MODULES ---
 
20
  try:
21
  from module.Validation_Contract import ETL_Engine
 
22
  from module.Dashboard import RealtimeMonitor, CrisisSimulator
23
  from module.Data_Exploration import HospitalAdvancedAnalyzer
24
  from module.Indicateurs_DQ import HospitalDataQualityAnalyzer, load_from_google_sheets
 
77
  "get_data_quality_report": {"func": self._get_dq_stats_wrapper, "desc": "Audit : Fiabilité des données."}
78
  }
79
 
80
+ # --- WRAPPERS (Extraction de données brutes) ---
81
 
82
  def _run_etl_wrapper(self, args=None):
83
  try:
 
85
  success, result = engine.run_etl()
86
  if success:
87
  st.session_state.data_explorer = result
 
88
  return {"status": "succès", "details": result.get_summary()}
89
  return {"status": "erreur", "message": "Échec critique du pipeline ETL."}
90
  except Exception as e: return {"status": "crash", "message": str(e)}
 
96
  df_final, msg = engineer.build_full_dataset()
97
  if df_final is not None:
98
  st.session_state['ml_features'] = df_final
99
+ return {"status": "succès", "details": {"lignes": len(df_final), "features": len(df_final.columns)}}
 
 
 
100
  return {"status": "erreur", "message": msg}
101
  except Exception as e: return {"status": "crash", "message": str(e)}
102
 
 
108
  metrics = forecaster.train_all_models()
109
  st.session_state['forecaster'] = forecaster
110
  st.session_state['metrics'] = metrics
111
+ best = forecaster.best_model_name
112
 
113
+ # On retourne les détails du meilleur modèle
114
+ return {"status": "succès", "details": {"best_model": best, "r2": metrics[best]['R²'], "mae": metrics[best]['MAE']}}
115
  except Exception as e: return {"status": "crash", "message": str(e)}
116
 
117
  def _get_forecast_wrapper(self, args=None):
118
  try:
119
  if 'forecaster' not in st.session_state: return {"status": "erreur", "message": "IA non entraînée."}
120
  forecaster = st.session_state['forecaster']
 
121
  dates, values, conf = forecaster.generate_future_forecast_recursive(months=3)
122
  alerts = forecaster.detect_future_anomalies(values)
 
 
123
  trend_pct = ((values[-1] - values[0]) / values[0]) * 100
124
 
125
  return {
126
  "status": "succès",
127
  "details": {
128
  "tendance_pct": trend_pct,
 
129
  "max_val": float(np.max(values)),
130
+ "alertes": alerts
 
 
131
  }
132
  }
133
  except Exception as e: return {"status": "crash", "message": str(e)}
134
 
135
  def _get_kpis_wrapper(self, args=None):
136
  try:
 
137
  if 'ml_features' not in st.session_state or 'forecaster' not in st.session_state:
138
  return {"status": "erreur", "message": "Le Cockpit nécessite les données ML et le modèle entraîné."}
139
 
140
  monitor = RealtimeMonitor(st.session_state['ml_features'], st.session_state['forecaster'])
141
+ state = monitor.current_state
142
 
143
+ # Extraction propre du service en tension
144
  services_breakdown = monitor.get_service_breakdown()
145
+ if services_breakdown is not None and not services_breakdown.empty:
146
+ # On s'assure que c'est une string propre
147
+ top_tension_service = str(services_breakdown.index[0])
148
+ else:
149
+ top_tension_service = "Global"
150
 
 
151
  state['top_tension_service'] = top_tension_service
152
 
153
  return {"status": "succès", "details": state}
 
159
  if not analyzer.load_data(): return {"status": "erreur", "message": "Données historiques inaccessibles."}
160
 
161
  df_pat = analyzer.get_df('patients')
162
+ df_sej = analyzer.get_df('sejours')
163
  stats = {}
164
+
165
  if df_pat is not None:
166
  df_pat['age'] = pd.to_numeric(df_pat['age'], errors='coerce')
167
  stats['demographie'] = {
168
  'total': len(df_pat),
169
  'age_moyen': round(df_pat['age'].mean(), 1),
170
+ 'senior_ratio': round((df_pat['age'] > 65).mean() * 100, 1),
171
+ 'sex_ratio': df_pat['sexe'].value_counts(normalize=True).to_dict()
172
  }
173
+
174
+ if df_sej is not None:
175
+ stats['pathologie'] = df_sej['pathologie_code'].mode()[0] if not df_sej.empty else "N/A"
176
+
177
  return {"status": "succès", "details": stats}
178
  except Exception as e: return {"status": "crash", "message": str(e)}
179
 
 
182
  raw_data = load_from_google_sheets(self.gspread_client, "bdd_mvp_faker")
183
  dq = HospitalDataQualityAnalyzer(raw_data)
184
  scores = dq.compute_global_score()
185
+ details_anomalies = {k: int(v['anomalies']) for k, v in dq.metrics.items() if 'anomalies' in v}
 
 
186
 
187
  return {
188
  "status": "succès",
 
193
  }
194
  except Exception as e: return {"status": "crash", "message": str(e)}
195
 
196
+ # --- CERVEAU ANALYTIQUE (Le Cœur du Système) ---
197
  def _generate_human_response(self, tool_name, d):
198
  """
199
+ Traduit les données techniques en analyse de direction.
200
+ Structure: Constat -> Analyse -> Stratégie.
201
  """
202
 
203
+ # 1. MONITORING TEMPS RÉEL (COCKPIT)
204
  if tool_name == "get_hospital_kpis":
205
  taux = d.get('taux_occupation', 0) * 100
206
  trend = d.get('trend_occupation', 0)
207
+ score = min(d.get('health_score', 100), 100) # Clamping pour éviter 182/100
208
  tension_rh = d.get('tension_personnel', 0)
209
  service_top = d.get('top_tension_service', 'N/A')
210
 
211
  msg = "### 🚁 Rapport de Situation Temps Réel\n\n"
212
+ msg += f"**État Global :** Score de santé de **{score:.0f}/100**.\n"
213
+ msg += f"- Occupation : **{taux:.1f}%** (Tendance 72h: {trend:+.1f}%)\n"
214
+ msg += f"- Tension RH : **{tension_rh:.2f}** (Seuil critique > 1.2)\n\n"
215
 
216
+ msg += "**🧠 Analyse :** "
217
+ if taux > 90: msg += "Saturation imminente. L'inertie des admissions risque de bloquer les urgences. "
218
+ elif taux > 80: msg += "Activité dense. Les capacités tampons sont entamées. "
219
+ else: msg += "Situation fluide, capacités d'absorption disponibles. "
220
 
221
+ msg += f"Le point de vigilance est le service **{service_top}**.\n\n"
222
+
223
+ msg += "**🎯 Recommandations :**\n"
224
+ if score < 70:
225
+ msg += f"1. Priorité absolue : Désengorger le service {service_top}.\n"
226
+ msg += "2. Activer les lits supplémentaires (Plan Niveau 1).\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
  else:
228
+ msg += "1. Maintenir la surveillance standard.\n2. Optimiser les sorties pour lisser la charge de demain."
 
229
  return msg
230
 
231
+ # 2. PRÉDICTIONS (FORECASTING)
232
  elif tool_name == "get_future_forecast":
233
  tendance = d['tendance_pct']
234
  max_val = d['max_val']
235
  alertes = d['alertes']
236
 
237
+ msg = "### 🔮 Note Prospective (3 Mois)\n\n"
 
 
238
  direction = "HAUSSE" if tendance > 0 else "BAISSE"
239
+ msg += f"**Trajectoire :** Anticipation d'une **{direction} de {abs(tendance):.1f}%**.\n"
240
+ msg += f"**Pic attendu :** {max_val:.0f} admissions/jour.\n\n"
241
 
242
+ msg += "**🧠 Analyse des Risques :** "
 
243
  if len(alertes) > 0:
244
+ msg += f"Je détecte **{len(alertes)} anomalies statistiques** à venir. "
245
+ msg += f"La première tension critique arrivera à **J+{alertes[0]['day']}**.\n\n"
 
 
246
  else:
247
+ msg += "Aucune anomalie majeure détectée. Le flux reste dans le tunnel de normalité.\n\n"
248
 
249
+ msg += "**🎯 Actions :**\n"
250
+ if tendance > 10:
251
+ msg += "1. Valider les plannings RH pour couvrir la hausse.\n2. Augmenter les stocks de consommables (+15%)."
 
 
 
252
  else:
253
+ msg += "1. Optimiser le taux de remplissage des lits programmés."
 
 
254
  return msg
255
 
256
+ # 3. QUALITÉ DES DONNÉES (AUDIT)
257
  elif tool_name == "get_data_quality_report":
258
  scores = d['scores']
259
+ anomalies_dict = d['anomalies_par_table']
260
+ total_anomalies = sum(anomalies_dict.values()) # Correction du bug "not defined"
261
 
262
+ msg = "### 🛡️ Audit Qualité des Données\n\n"
263
+ msg += f"**Indice de Confiance : {scores['global']}/100**\n"
264
+ msg += f"- Complétude : {scores['completeness']:.1f}%\n- Exactitude : {scores['accuracy']:.1f}%\n\n"
265
 
266
+ msg += "**🧠 Diagnostic :** "
267
  if total_anomalies > 0:
268
+ worst_table = max(anomalies_dict, key=anomalies_dict.get)
269
+ msg += f"J'ai identifié **{total_anomalies} anomalies** qui 'bruitent' les analyses. "
270
+ msg += f"La source principale d'erreur est la table **'{worst_table}'** ({anomalies_dict[worst_table]} erreurs).\n\n"
 
271
  else:
272
+ msg += "Les données sont propres et fiables pour l'IA.\n\n"
273
+
274
+ msg += "**🎯 Remédiation :**\n"
275
+ if scores['global'] < 85:
276
+ msg += "1. Demander une purge des doublons et erreurs de dates.\n2. Relancer l'ETL après correction."
 
 
277
  else:
278
+ msg += "Rien à signaler. Maintenir les processus actuels."
 
279
  return msg
280
 
281
+ # 4. EXPLORATION (HISTORIQUE) - NOUVEAU BLOC AJOUTÉ
282
+ elif tool_name == "analyze_patient_stats":
283
+ demo = d.get('demographie', {})
284
+ patho = d.get('pathologie', 'N/A')
285
+
286
+ msg = "### 🔬 Analyse Démographique & Clinique\n\n"
287
+ msg += f"**Cohorte :** {demo.get('total', 0)} patients analysés.\n"
288
+ msg += f"**Profil Type :** Âge moyen de **{demo.get('age_moyen', 0)} ans**.\n"
289
+ msg += f"**Gériatrie :** {demo.get('senior_ratio', 0)}% des patients ont plus de 65 ans.\n"
290
+ msg += f"**Pathologie Dominante :** {patho}.\n\n"
291
+
292
+ msg += "**🧠 Interprétation :** "
293
+ if demo.get('senior_ratio', 0) > 40:
294
+ msg += "La population est vieillissante, ce qui implique des durées de séjour plus longues et une consommation de soins plus élevée. "
295
+ else:
296
+ msg += "La population est relativement jeune, favorisant un turn-over rapide (ambulatoire). "
297
+
298
+ msg += "\n\n**🎯 Impact Stratégique :**\n"
299
+ msg += "1. Adapter les ratios soignants en fonction de la lourdeur des soins (liée à l'âge).\n"
300
+ msg += "2. Vérifier l'adéquation des équipements pour la pathologie dominante."
301
+ return msg
302
+
303
+ # 5. ETL (Mise à jour) - NOUVEAU BLOC
304
+ elif tool_name == "run_etl_pipeline":
305
+ return f"✅ **ETL Terminé :** Base de données synchronisée.\n- Lignes traitées : {d['total_rows_clean']:,}\n- Qualité : Nettoyage effectué avec succès."
306
+
307
+ # 6. TRAINING (IA) - NOUVEAU BLOC
308
+ elif tool_name == "train_forecasting_models":
309
+ r2 = d['r2'] * 100
310
+ return f"✅ **IA Mise à Jour :** Le modèle champion est **{d['best_model']}**.\n- Fiabilité (R²) : **{r2:.1f}%**\n- Précision (MAE) : +/- {d['mae']:.1f} admissions/jour."
311
+
312
+ # 7. FEATURES - NOUVEAU BLOC
313
+ elif tool_name == "run_feature_engineering":
314
+ return f"✅ **Préparation ML :** Dataset généré.\n- {d['lignes']:,} vecteurs d'apprentissage\n- {d['features']} variables explicatives créées."
315
+
316
+ return "Commande exécutée avec succès."
317
 
318
  # --- CERVEAU PRINCIPAL ---
319
  def ask(self, user_message, chat_history=[]):
320
  system_prompt = f"""
321
  ROLE: Tu es Jasmine, la Directrice des Opérations (COO) Virtuelle de l'hôpital.
322
+ TON: Autoritaire mais bienveillant, précis, orienté solution.
323
 
324
  OUTILS DISPONIBLES:
325
  {chr(10).join([f"- {k}: {v['desc']}" for k, v in self.tools_map.items()])}
326
 
327
+ RÈGLE D'OR:
328
+ Chaque réponse doit contenir :
329
+ 1. Les Faits (Chiffres)
330
+ 2. L'Analyse (Pourquoi ?)
331
+ 3. La Stratégie (Quoi faire ?)
332
 
333
+ FORMAT DE RÉPONSE JSON:
334
  {{
335
+ "thought": "Raisonnement interne...",
336
+ "tool": "nom_outil",
337
  "args": {{}},
338
+ "answer": "Texte si pas d'outil"
339
  }}
340
  """
341
 
 
357
  break
358
  except: continue
359
 
360
+ if not response_payload: return "❌ **Erreur Critique :** Services cognitifs indisponibles.", None, None
361
 
362
  try:
363
  decision = json.loads(re.sub(r'```json\s*|\s*```', '', response_payload).strip())
 
367
  if tool_name in self.tools_map:
368
  res = self.tools_map[tool_name]["func"]({})
369
  if res["status"] == "succès":
370
+ # Appel de la fonction de réponse humaine enrichie
371
  final_response = self._generate_human_response(tool_name, res["details"])
372
  else:
373
  final_response = f"⚠️ **Alerte Technique :** {res.get('message')}"
 
390
  if msg.get("data"):
391
  with st.expander("💾 SYSTEM PAYLOAD"): st.json(msg["data"])
392
 
393
+ user_input = st.chat_input("Ordre à l'IA (ex: 'Audit Qualité', 'Situation RH', 'Analyse Patients')...")
394
  if user_input:
395
  st.session_state.chat_history.append({"role": "user", "content": user_input})
396
  st.rerun()