jaimalklyde commited on
Commit
d802c2d
·
verified ·
1 Parent(s): 7f60eba

Update src/module/AIP.py

Browse files
Files changed (1) hide show
  1. src/module/AIP.py +76 -38
src/module/AIP.py CHANGED
@@ -1,8 +1,10 @@
1
  """
2
- MODULE JASMINE AGENT - VERSION ROBUSTE (FALLBACK INTELLIGENT)
3
- =============================================================
4
  Responsabilité : Agent IA avec cascade de modèles de secours.
5
- Capacité : Pilotage de l'ETL (Validation_Contract) pour recharger les données.
 
 
6
  """
7
 
8
  import google.generativeai as genai
@@ -13,11 +15,12 @@ import re
13
  import os
14
  import traceback
15
 
16
- # --- IMPORT DU MODULE CIBLE ---
17
  try:
18
  from module.Validation_Contract import ETL_Engine
 
19
  except ImportError:
20
- st.error("❌ Erreur critique : Le module Validation_Contract est introuvable.")
21
 
22
  # --- STYLE GOTHAM / AIP TERMINAL (RECTANGULAR & DARK) ---
23
  def apply_custom_styles():
@@ -59,7 +62,7 @@ def apply_custom_styles():
59
  color: #8b949e;
60
  background: #0d1117;
61
  padding: 10px;
62
- border: 1px solid #30363d;
63
  border-radius: 0px;
64
  margin-top: 5px;
65
  margin-bottom: 10px;
@@ -93,33 +96,25 @@ class JasmineAgent:
93
  def __init__(self, gspread_client):
94
  self.gspread_client = gspread_client
95
 
96
- # --- CORRECTION : ACCÈS SECRETS HUGGING FACE VIA OS ---
97
- # Sur Hugging Face, les secrets sont des variables d'environnement.
98
- # st.secrets plante s'il ne trouve pas de fichier .toml.
99
-
100
  self.google_key = os.environ.get("GOOGLE_API_KEY")
101
  self.groq_key = os.environ.get("GROQ_API_KEY")
102
 
103
- # Fallback : Si os.environ est vide, on tente st.secrets (cas local)
104
- # avec un try/except pour ne pas faire crasher l'app
105
  if not self.google_key:
106
- try:
107
- self.google_key = st.secrets.get("GOOGLE_API_KEY")
108
- except Exception:
109
- pass # On ignore si pas trouvé
110
 
111
  if not self.groq_key:
112
- try:
113
- self.groq_key = st.secrets.get("GROQ_API_KEY")
114
- except Exception:
115
- pass
116
 
117
  # Clients
118
  self.groq_client = Groq(api_key=self.groq_key) if self.groq_key else None
119
  if self.google_key:
120
  genai.configure(api_key=self.google_key)
121
 
122
- # ... (Le reste de __init__ avec MODEL_CASCADE reste inchangé) ...
123
  self.MODEL_CASCADE = [
124
  "gemini-2.0-flash-exp",
125
  "llama-3.3-70b-versatile",
@@ -127,10 +122,16 @@ class JasmineAgent:
127
  "llama-3.1-8b-instant"
128
  ]
129
 
 
130
  self.tools_map = {
131
  "run_etl_pipeline": {
132
  "description": "Lance le nettoyage complet des données (ETL). Indispensable pour recharger, mettre à jour ou vérifier la qualité des données.",
133
  "func": self._run_etl_wrapper
 
 
 
 
 
134
  }
135
  }
136
 
@@ -153,6 +154,28 @@ class JasmineAgent:
153
  except Exception as e:
154
  return {"status": "crash", "message": str(e)}
155
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  def build_system_prompt(self):
157
  tools_desc = "\n".join([f"- {k}: {v['description']}" for k, v in self.tools_map.items()])
158
  return f"""
@@ -164,7 +187,7 @@ class JasmineAgent:
164
 
165
  RÈGLES :
166
  1. Utilise 'run_etl_pipeline' si l'utilisateur demande une mise à jour, un refresh ou une vérification des données.
167
- 2. Si aucune action technique n'est requise, réponds poliment.
168
  3. Réponds TOUJOURS au format JSON strict :
169
  {{
170
  "thought": "Analyse de la demande...",
@@ -182,7 +205,7 @@ class JasmineAgent:
182
  response_payload = None
183
  used_model = None
184
 
185
- # --- BOUCLE DE SURVIE ---
186
  for model_name in self.MODEL_CASCADE:
187
  try:
188
  # A. CAS GOOGLE GEMINI
@@ -190,7 +213,6 @@ class JasmineAgent:
190
  if not self.google_key: continue
191
 
192
  model = genai.GenerativeModel(model_name)
193
- # Gemini prend le system prompt dans le user prompt pour le moment ou via config
194
  full_prompt = f"SYSTEM: {system_prompt}\nUSER: {user_message}"
195
 
196
  res = model.generate_content(
@@ -239,17 +261,35 @@ class JasmineAgent:
239
 
240
  if tool_name in self.tools_map:
241
  tool_result = self.tools_map[tool_name]["func"]({})
 
242
  if tool_result["status"] == "succès":
243
  stats = tool_result["details"]
244
- final_response = (f"✅ **Action `{tool_name}` réussie.**\n\n"
245
- f"- Tables chargées : {stats['tables_loaded']}\n"
246
- f"- Lignes valides : {stats['total_rows_clean']:,}\n"
247
- f"- Mémoire utilisée : {stats['total_memory_mb']} MB")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
  else:
249
  final_response = f"⚠️ Problème technique : {tool_result['message']}"
250
 
251
  elif tool_name == "none":
252
- final_response = "Je suis à l'écoute. Pour l'instant, je ne peux que lancer l'ETL (mise à jour des données)."
253
 
254
  return final_response, thought, tool_result
255
 
@@ -258,9 +298,10 @@ class JasmineAgent:
258
 
259
 
260
  # --- FONCTION D'AFFICHAGE (INTERFACE) ---
261
- def show_jasmine_interface(client, sheet_name):
 
262
  apply_custom_styles()
263
- st.markdown("## 💬 JASMINE : ETL Controller")
264
  st.caption("Agent intelligent avec redondance multi-modèles (Gemini 2.0 / Llama 3.3).")
265
 
266
  if 'agent_instance' not in st.session_state:
@@ -271,32 +312,29 @@ def show_jasmine_interface(client, sheet_name):
271
 
272
  # Historique
273
  for msg in st.session_state.chat_history:
274
- # Choix de la classe CSS
275
  role_class = "user-msg" if msg["role"] == "user" else "bot-msg"
276
-
277
- # Affichage du message principal
278
  st.markdown(f'<div class="{role_class}">{msg["content"]}</div>', unsafe_allow_html=True)
279
 
280
- # Affichage du "Cerveau" (Thought Trace) si disponible
281
  if msg.get("thought"):
282
  with st.expander("👁️ AIP LOGIC TRACE"):
283
  st.markdown(f'<div class="debug-block">{msg["thought"]}</div>', unsafe_allow_html=True)
284
 
285
- # Affichage des données techniques (JSON)
286
  if msg.get("data"):
287
  with st.expander("�� SYSTEM PAYLOAD"):
288
  st.json(msg["data"])
289
 
290
  # Input
291
- user_input = st.chat_input("Dites 'Mets à jour les données'...")
292
 
293
  if user_input:
294
  st.session_state.chat_history.append({"role": "user", "content": user_input})
295
  st.rerun()
296
 
297
  if st.session_state.chat_history and st.session_state.chat_history[-1]["role"] == "user":
 
 
298
  with st.spinner("Jasmine consulte ses modèles..."):
299
- response, thought, data = st.session_state.agent_instance.ask(st.session_state.chat_history[-1]["content"])
300
 
301
  st.session_state.chat_history.append({
302
  "role": "assistant",
 
1
  """
2
+ MODULE JASMINE AGENT - VERSION ROBUSTE (FALLBACK INTELLIGENT + DASHBOARD)
3
+ =========================================================================
4
  Responsabilité : Agent IA avec cascade de modèles de secours.
5
+ Capacités :
6
+ 1. Pilotage de l'ETL (Mise à jour données)
7
+ 2. Lecture du Dashboard (Consultation KPIs)
8
  """
9
 
10
  import google.generativeai as genai
 
15
  import os
16
  import traceback
17
 
18
+ # --- IMPORT DES MODULES CIBLES ---
19
  try:
20
  from module.Validation_Contract import ETL_Engine
21
+ from module.Dashboard import HospitalDashboard # <--- AJOUT CRITIQUE
22
  except ImportError:
23
+ st.error("❌ Erreur critique : Impossible d'importer ETL_Engine ou HospitalDashboard.")
24
 
25
  # --- STYLE GOTHAM / AIP TERMINAL (RECTANGULAR & DARK) ---
26
  def apply_custom_styles():
 
62
  color: #8b949e;
63
  background: #0d1117;
64
  padding: 10px;
65
+ border: 1px solid #30363d;
66
  border-radius: 0px;
67
  margin-top: 5px;
68
  margin-bottom: 10px;
 
96
  def __init__(self, gspread_client):
97
  self.gspread_client = gspread_client
98
 
99
+ # --- CONFIGURATION SECRETS (Compatible Hugging Face) ---
 
 
 
100
  self.google_key = os.environ.get("GOOGLE_API_KEY")
101
  self.groq_key = os.environ.get("GROQ_API_KEY")
102
 
103
+ # Fallback local (st.secrets)
 
104
  if not self.google_key:
105
+ try: self.google_key = st.secrets.get("GOOGLE_API_KEY")
106
+ except: pass
 
 
107
 
108
  if not self.groq_key:
109
+ try: self.groq_key = st.secrets.get("GROQ_API_KEY")
110
+ except: pass
 
 
111
 
112
  # Clients
113
  self.groq_client = Groq(api_key=self.groq_key) if self.groq_key else None
114
  if self.google_key:
115
  genai.configure(api_key=self.google_key)
116
 
117
+ # --- CASCADE DE MODÈLES (VOTRE LISTE EXACTE) ---
118
  self.MODEL_CASCADE = [
119
  "gemini-2.0-flash-exp",
120
  "llama-3.3-70b-versatile",
 
122
  "llama-3.1-8b-instant"
123
  ]
124
 
125
+ # --- OUTILS DISPONIBLES (ETL + DASHBOARD) ---
126
  self.tools_map = {
127
  "run_etl_pipeline": {
128
  "description": "Lance le nettoyage complet des données (ETL). Indispensable pour recharger, mettre à jour ou vérifier la qualité des données.",
129
  "func": self._run_etl_wrapper
130
+ },
131
+ # --- NOUVEL OUTIL AJOUTÉ ---
132
+ "get_hospital_kpis": {
133
+ "description": "Récupère les indicateurs clés (KPIs) actuels de l'hôpital : occupation des lits, tension RH, admissions du jour, ruptures de stock.",
134
+ "func": self._get_kpis_wrapper
135
  }
136
  }
137
 
 
154
  except Exception as e:
155
  return {"status": "crash", "message": str(e)}
156
 
157
+ # --- NOUVEAU WRAPPER DASHBOARD ---
158
+ def _get_kpis_wrapper(self, args=None):
159
+ """Connecteur vers le module Dashboard pour lire les chiffres"""
160
+ try:
161
+ # On instancie le Dashboard (Lecture seule)
162
+ dash = HospitalDashboard(self.gspread_client, "bdd_mvp_faker")
163
+
164
+ # Chargement des données
165
+ if not dash.load_data():
166
+ return {"status": "erreur", "message": "Impossible de lire les données. L'ETL a-t-il tourné ?"}
167
+
168
+ # Récupération des KPIs calculés
169
+ kpis = dash._get_kpis()
170
+
171
+ return {
172
+ "status": "succès",
173
+ "message": "KPIs récupérés.",
174
+ "details": kpis
175
+ }
176
+ except Exception as e:
177
+ return {"status": "crash", "message": f"Erreur Dashboard: {str(e)}"}
178
+
179
  def build_system_prompt(self):
180
  tools_desc = "\n".join([f"- {k}: {v['description']}" for k, v in self.tools_map.items()])
181
  return f"""
 
187
 
188
  RÈGLES :
189
  1. Utilise 'run_etl_pipeline' si l'utilisateur demande une mise à jour, un refresh ou une vérification des données.
190
+ 2. Utilise 'get_hospital_kpis' si l'utilisateur demande l'état de l'hôpital, les chiffres clés, l'occupation ou la tension.
191
  3. Réponds TOUJOURS au format JSON strict :
192
  {{
193
  "thought": "Analyse de la demande...",
 
205
  response_payload = None
206
  used_model = None
207
 
208
+ # --- BOUCLE DE SURVIE (FALLBACK) ---
209
  for model_name in self.MODEL_CASCADE:
210
  try:
211
  # A. CAS GOOGLE GEMINI
 
213
  if not self.google_key: continue
214
 
215
  model = genai.GenerativeModel(model_name)
 
216
  full_prompt = f"SYSTEM: {system_prompt}\nUSER: {user_message}"
217
 
218
  res = model.generate_content(
 
261
 
262
  if tool_name in self.tools_map:
263
  tool_result = self.tools_map[tool_name]["func"]({})
264
+
265
  if tool_result["status"] == "succès":
266
  stats = tool_result["details"]
267
+
268
+ # --- RÉPONSE INTELLIGENTE SELON L'OUTIL ---
269
+ if tool_name == "run_etl_pipeline":
270
+ final_response = (f"✅ **Mise à jour ETL réussie.**\n\n"
271
+ f"- Tables chargées : {stats['tables_loaded']}\n"
272
+ f"- Lignes valides : {stats['total_rows_clean']:,}")
273
+
274
+ elif tool_name == "get_hospital_kpis":
275
+ # Jasmine commente les chiffres
276
+ taux = stats.get('taux_global', 0)
277
+ tension = stats.get('tension_rh', 0)
278
+
279
+ etat_global = "🟢 Stable"
280
+ if taux > 85: etat_global = "🟠 Tendu"
281
+ if taux > 95: etat_global = "🔴 Critique"
282
+
283
+ final_response = (f"📊 **Situation Hôpital ({etat_global}) :**\n\n"
284
+ f"🔹 **Occupation :** {taux:.1f}%\n"
285
+ f"🔹 **Lits :** {stats.get('lits_occupes')} occupés / {stats.get('lits_total')} ouverts\n"
286
+ f"🔹 **Tension RH :** {tension:.1f}%\n"
287
+ f"🔹 **Alertes Stocks :** {stats.get('nb_ruptures')}")
288
  else:
289
  final_response = f"⚠️ Problème technique : {tool_result['message']}"
290
 
291
  elif tool_name == "none":
292
+ final_response = "Je suis à l'écoute. Je peux lancer l'ETL ou analyser les KPIs de l'hôpital."
293
 
294
  return final_response, thought, tool_result
295
 
 
298
 
299
 
300
  # --- FONCTION D'AFFICHAGE (INTERFACE) ---
301
+ # J'ai renommé en show_terminal pour matcher streamlit_app.py
302
+ def show_terminal(client, sheet_name="bdd_mvp_faker"):
303
  apply_custom_styles()
304
+ st.markdown("## 🤖 AIP COMMAND TERMINAL")
305
  st.caption("Agent intelligent avec redondance multi-modèles (Gemini 2.0 / Llama 3.3).")
306
 
307
  if 'agent_instance' not in st.session_state:
 
312
 
313
  # Historique
314
  for msg in st.session_state.chat_history:
 
315
  role_class = "user-msg" if msg["role"] == "user" else "bot-msg"
 
 
316
  st.markdown(f'<div class="{role_class}">{msg["content"]}</div>', unsafe_allow_html=True)
317
 
 
318
  if msg.get("thought"):
319
  with st.expander("👁️ AIP LOGIC TRACE"):
320
  st.markdown(f'<div class="debug-block">{msg["thought"]}</div>', unsafe_allow_html=True)
321
 
 
322
  if msg.get("data"):
323
  with st.expander("�� SYSTEM PAYLOAD"):
324
  st.json(msg["data"])
325
 
326
  # Input
327
+ user_input = st.chat_input("Dites 'Mets à jour les données' ou 'Donne moi l'état de l'hôpital'...")
328
 
329
  if user_input:
330
  st.session_state.chat_history.append({"role": "user", "content": user_input})
331
  st.rerun()
332
 
333
  if st.session_state.chat_history and st.session_state.chat_history[-1]["role"] == "user":
334
+ last_prompt = st.session_state.chat_history[-1]["content"]
335
+
336
  with st.spinner("Jasmine consulte ses modèles..."):
337
+ response, thought, data = st.session_state.agent_instance.ask(last_prompt)
338
 
339
  st.session_state.chat_history.append({
340
  "role": "assistant",