Sahm269 commited on
Commit
24ae56d
·
verified ·
1 Parent(s): d4d668f

Upload 4 files

Browse files
server/db/db.py CHANGED
@@ -1,5 +1,7 @@
1
  import streamlit as st
2
  import psycopg2
 
 
3
  from datetime import datetime
4
  import logging
5
  from typing import List, Dict
@@ -10,62 +12,206 @@ logging.basicConfig(level=logging.INFO, handlers=[logging.StreamHandler()])
10
  logger = logging.getLogger(__name__)
11
 
12
 
13
-
14
  # Fonction pour obtenir la connexion à la base de données
15
- def get_db_connection():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  try:
17
- conn = psycopg2.connect(
18
- host=st.secrets["DB_HOST"],
19
- port=st.secrets["DB_PORT"],
20
- dbname=st.secrets["DB_NAME"],
21
- user=st.secrets["DB_USER"],
22
- password=st.secrets["DB_PASSWORD"]
23
- )
24
  return conn
25
- except Exception as e:
26
- logger.error(f"Erreur de connexion à la base de données: {e}")
27
  return None
28
 
 
29
  # Connexion à la base de données pour récupérer le nombre total de recettes
30
- def get_recipes_count():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  conn = get_db_connection()
 
 
32
  if conn is None:
33
  return 0
 
34
  try:
 
35
  cursor = conn.cursor()
 
 
36
  cursor.execute("SELECT COUNT(*) FROM suggestions_repas")
 
 
37
  result = cursor.fetchone()
 
 
38
  return result[0] # Le nombre total de recettes
39
- except Exception as e:
 
 
40
  logger.error(f"Erreur lors de la récupération du nombre de recettes : {e}")
41
  return 0
 
42
  finally:
 
43
  cursor.close()
44
  conn.close()
45
 
 
46
  # Fonction pour récupérer la latence moyenne des messages
47
- def get_average_latency():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  conn = get_db_connection()
 
 
49
  if conn is None:
50
  return 0.0
 
51
  try:
 
52
  cursor = conn.cursor()
53
- cursor.execute("SELECT AVG(temps_traitement) FROM messages WHERE temps_traitement IS NOT NULL")
 
 
 
 
 
 
54
  result = cursor.fetchone()
 
 
55
  return round(result[0], 2) if result[0] is not None else 0.0
56
- except Exception as e:
 
 
57
  logger.error(f"Erreur de connexion à la base de données pour la latence : {e}")
58
  return 0.0
 
59
  finally:
 
60
  cursor.close()
61
  conn.close()
62
 
 
63
  # Fonction pour récupérer le nombre de requêtes par jour
64
- def get_daily_requests():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  conn = get_db_connection()
 
 
66
  if conn is None:
67
  return pd.DataFrame()
 
68
  try:
 
69
  query = """
70
  SELECT
71
  DATE(timestamp) AS date,
@@ -77,61 +223,196 @@ def get_daily_requests():
77
  ORDER BY
78
  date;
79
  """
 
 
80
  df = pd.read_sql(query, conn)
 
 
81
  return df
82
- except Exception as e:
 
 
83
  logger.error(f"Erreur lors de la récupération des requêtes par jour : {e}")
84
  return pd.DataFrame()
 
85
  finally:
 
86
  conn.close()
87
 
88
 
89
  # Fonction pour récupérer les ingrédients depuis la base de données
90
- def get_ingredients():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  conn = get_db_connection()
 
 
 
 
 
92
  cursor = conn.cursor()
 
93
  try:
 
94
  cursor.execute("SELECT ingredients FROM liste_courses")
95
- ingredients_list = cursor.fetchall() # Récupère tous les résultats
96
- return ingredients_list
97
- except Exception as e:
98
- logger.error(f"Erreur lors de la récupération des requêtes par jour : {e}")
99
- return pd.DataFrame()
 
 
 
 
 
 
 
100
  finally:
101
- # Fermer la connexion
102
  cursor.close()
103
  conn.close()
104
 
 
105
  # Fonction pour récupérer le coût total des requêtes
106
- def get_total_cost():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  conn = get_db_connection()
 
 
108
  if conn is None:
109
  return 0.0
 
 
 
110
  try:
111
- cursor = conn.cursor()
112
- cursor.execute("SELECT SUM(total_cout) FROM messages WHERE total_cout IS NOT NULL")
 
 
 
 
113
  result = cursor.fetchone()
 
 
114
  return round(result[0], 2) if result[0] is not None else 0.0
115
- except Exception as e:
 
 
116
  logger.error(f"Erreur lors de la récupération du coût total : {e}")
117
  return 0.0
 
118
  finally:
 
119
  cursor.close()
120
  conn.close()
121
 
 
122
  # Fonction pour récupérer l'impact écologique estimé
123
- def get_total_impact():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  conn = get_db_connection()
 
 
125
  if conn is None:
126
  return 0.0
 
 
 
127
  try:
128
- cursor = conn.cursor()
129
- cursor.execute("SELECT SUM(impact_eco) FROM messages WHERE impact_eco IS NOT NULL")
 
 
 
 
130
  result = cursor.fetchone()
 
 
131
  return round(result[0], 2) if result[0] is not None else 0.0
132
- except Exception as e:
 
 
133
  logger.error(f"Erreur lors de la récupération de l'impact écologique : {e}")
134
  return 0.0
 
135
  finally:
 
136
  cursor.close()
137
  conn.close()
 
1
  import streamlit as st
2
  import psycopg2
3
+ import sqlite3
4
+ from sqlite3 import Connection
5
  from datetime import datetime
6
  import logging
7
  from typing import List, Dict
 
12
  logger = logging.getLogger(__name__)
13
 
14
 
 
15
  # Fonction pour obtenir la connexion à la base de données
16
+ # def get_db_connection():
17
+ # try:
18
+ # conn = psycopg2.connect(
19
+ # host=st.secrets["DB_HOST"],
20
+ # port=st.secrets["DB_PORT"],
21
+ # dbname=st.secrets["DB_NAME"],
22
+ # user=st.secrets["DB_USER"],
23
+ # password=st.secrets["DB_PASSWORD"]
24
+ # )
25
+ # return conn
26
+ # except Exception as e:
27
+ # logger.error(f"Erreur de connexion à la base de données: {e}")
28
+ # return None
29
+
30
+
31
+ def get_db_connection() -> Connection:
32
+ """
33
+ Établit une connexion avec la base SQLite.
34
+
35
+ Returns:
36
+ Connection : le client de connexion à la base.
37
+ """
38
  try:
39
+ conn = sqlite3.connect(
40
+ st.secrets["DB_NAME"], check_same_thread=False
41
+ ) # Spécifiez ici le chemin de votre fichier SQLite
42
+ conn.row_factory = sqlite3.Row # Pour des résultats sous forme de dictionnaire
 
 
 
43
  return conn
44
+ except sqlite3.Error as e:
45
+ logger.error(f"Erreur de connexion à la base de données SQLite: {e}")
46
  return None
47
 
48
+
49
  # Connexion à la base de données pour récupérer le nombre total de recettes
50
+ # def get_recipes_count():
51
+ # conn = get_db_connection()
52
+ # if conn is None:
53
+ # return 0
54
+ # try:
55
+ # cursor = conn.cursor()
56
+ # cursor.execute("SELECT COUNT(*) FROM suggestions_repas")
57
+ # result = cursor.fetchone()
58
+ # return result[0] # Le nombre total de recettes
59
+ # except Exception as e:
60
+ # logger.error(f"Erreur lors de la récupération du nombre de recettes : {e}")
61
+ # return 0
62
+ # finally:
63
+ # cursor.close()
64
+ # conn.close()
65
+
66
+
67
+ def get_recipes_count() -> int:
68
+ """
69
+ Récupère le nombre total de recettes enregistrées dans la table `suggestions_repas` de la base de données SQLite.
70
+
71
+ Cette fonction se connecte à la base de données SQLite, exécute une requête pour compter le nombre d'entrées
72
+ dans la table `suggestions_repas`, puis retourne ce nombre.
73
+
74
+ Returns:
75
+ int: Le nombre total de recettes dans la table `suggestions_repas`. Retourne 0 en cas d'erreur ou si la connexion échoue.
76
+ """
77
+ # Connexion à la base de données SQLite
78
  conn = get_db_connection()
79
+
80
+ # Si la connexion échoue, on retourne 0
81
  if conn is None:
82
  return 0
83
+
84
  try:
85
+ # Création du curseur pour exécuter la requête
86
  cursor = conn.cursor()
87
+
88
+ # Exécution de la requête SQL pour compter les entrées de recettes dans la table 'suggestions_repas'
89
  cursor.execute("SELECT COUNT(*) FROM suggestions_repas")
90
+
91
+ # Récupération du résultat de la requête
92
  result = cursor.fetchone()
93
+
94
+ # Retour du nombre total de recettes
95
  return result[0] # Le nombre total de recettes
96
+
97
+ except sqlite3.Error as e:
98
+ # En cas d'erreur, on enregistre l'erreur dans les logs et on retourne 0
99
  logger.error(f"Erreur lors de la récupération du nombre de recettes : {e}")
100
  return 0
101
+
102
  finally:
103
+ # Fermeture du curseur et de la connexion
104
  cursor.close()
105
  conn.close()
106
 
107
+
108
  # Fonction pour récupérer la latence moyenne des messages
109
+ # def get_average_latency():
110
+ # conn = get_db_connection()
111
+ # if conn is None:
112
+ # return 0.0
113
+ # try:
114
+ # cursor = conn.cursor()
115
+ # cursor.execute("SELECT AVG(temps_traitement) FROM messages WHERE temps_traitement IS NOT NULL")
116
+ # result = cursor.fetchone()
117
+ # return round(result[0], 2) if result[0] is not None else 0.0
118
+ # except Exception as e:
119
+ # logger.error(f"Erreur de connexion à la base de données pour la latence : {e}")
120
+ # return 0.0
121
+ # finally:
122
+ # cursor.close()
123
+ # conn.close()
124
+
125
+
126
+ def get_average_latency() -> float:
127
+ """
128
+ Récupère la latence moyenne des traitements enregistrés dans la table `messages` de la base de données SQLite.
129
+
130
+ Cette fonction se connecte à la base de données SQLite, exécute une requête pour calculer la moyenne des valeurs
131
+ dans la colonne `temps_traitement` de la table `messages`, et retourne cette moyenne avec une précision de deux décimales.
132
+
133
+ Returns:
134
+ float: La latence moyenne des traitements en secondes. Retourne 0.0 en cas d'erreur ou si aucune donnée valide n'est disponible.
135
+ """
136
+ # Connexion à la base de données SQLite
137
  conn = get_db_connection()
138
+
139
+ # Si la connexion échoue, on retourne 0.0
140
  if conn is None:
141
  return 0.0
142
+
143
  try:
144
+ # Création du curseur pour exécuter la requête
145
  cursor = conn.cursor()
146
+
147
+ # Exécution de la requête SQL pour calculer la moyenne de la colonne 'temps_traitement'
148
+ cursor.execute(
149
+ "SELECT AVG(temps_traitement) FROM messages WHERE temps_traitement IS NOT NULL"
150
+ )
151
+
152
+ # Récupération du résultat de la requête
153
  result = cursor.fetchone()
154
+
155
+ # Retour de la moyenne arrondie à 2 décimales, ou 0.0 si aucun résultat
156
  return round(result[0], 2) if result[0] is not None else 0.0
157
+
158
+ except sqlite3.Error as e:
159
+ # En cas d'erreur, on enregistre l'erreur dans les logs et on retourne 0.0
160
  logger.error(f"Erreur de connexion à la base de données pour la latence : {e}")
161
  return 0.0
162
+
163
  finally:
164
+ # Fermeture du curseur et de la connexion
165
  cursor.close()
166
  conn.close()
167
 
168
+
169
  # Fonction pour récupérer le nombre de requêtes par jour
170
+ # def get_daily_requests():
171
+ # conn = get_db_connection()
172
+ # if conn is None:
173
+ # return pd.DataFrame()
174
+ # try:
175
+ # query = """
176
+ # SELECT
177
+ # DATE(timestamp) AS date,
178
+ # COUNT(*) AS nombre_requetes
179
+ # FROM
180
+ # messages
181
+ # GROUP BY
182
+ # date
183
+ # ORDER BY
184
+ # date;
185
+ # """
186
+ # df = pd.read_sql(query, conn)
187
+ # return df
188
+ # except Exception as e:
189
+ # logger.error(f"Erreur lors de la récupération des requêtes par jour : {e}")
190
+ # return pd.DataFrame()
191
+ # finally:
192
+ # conn.close()
193
+
194
+
195
+ def get_daily_requests() -> pd.DataFrame:
196
+ """
197
+ Récupère les requêtes quotidiennes à partir de la table `messages` de la base de données SQLite.
198
+
199
+ Cette fonction se connecte à la base de données SQLite, exécute une requête SQL pour compter le nombre de requêtes
200
+ (messages) par jour, et retourne les résultats sous forme de DataFrame pandas.
201
+
202
+ Returns:
203
+ pd.DataFrame: Un DataFrame contenant les dates et le nombre de requêtes pour chaque jour.
204
+ Retourne un DataFrame vide en cas d'erreur.
205
+ """
206
+ # Connexion à la base de données SQLite
207
  conn = get_db_connection()
208
+
209
+ # Si la connexion échoue, retourner un DataFrame vide
210
  if conn is None:
211
  return pd.DataFrame()
212
+
213
  try:
214
+ # Requête SQL pour récupérer le nombre de requêtes par jour
215
  query = """
216
  SELECT
217
  DATE(timestamp) AS date,
 
223
  ORDER BY
224
  date;
225
  """
226
+
227
+ # Exécution de la requête et récupération du résultat sous forme de DataFrame
228
  df = pd.read_sql(query, conn)
229
+
230
+ # Retour du DataFrame contenant les résultats
231
  return df
232
+
233
+ except sqlite3.Error as e:
234
+ # En cas d'erreur, on enregistre l'erreur dans les logs et on retourne un DataFrame vide
235
  logger.error(f"Erreur lors de la récupération des requêtes par jour : {e}")
236
  return pd.DataFrame()
237
+
238
  finally:
239
+ # Fermeture de la connexion à la base de données
240
  conn.close()
241
 
242
 
243
  # Fonction pour récupérer les ingrédients depuis la base de données
244
+ # def get_ingredients():
245
+ # conn = get_db_connection()
246
+ # cursor = conn.cursor()
247
+ # try:
248
+ # cursor.execute("SELECT ingredients FROM liste_courses")
249
+ # ingredients_list = cursor.fetchall() # Récupère tous les résultats
250
+ # return ingredients_list
251
+ # except Exception as e:
252
+ # logger.error(f"Erreur lors de la récupération des requêtes par jour : {e}")
253
+ # return pd.DataFrame()
254
+ # finally:
255
+ # # Fermer la connexion
256
+ # cursor.close()
257
+ # conn.close()
258
+
259
+
260
+ def get_ingredients() -> list:
261
+ """
262
+ Récupère la liste des ingrédients stockée dans la table `liste_courses` de la base de données SQLite.
263
+
264
+ Cette fonction se connecte à la base de données SQLite, exécute une requête SQL pour récupérer la colonne `ingredients`
265
+ de la table `liste_courses`, et retourne les résultats sous forme de liste.
266
+
267
+ Returns:
268
+ list: Une liste contenant les ingrédients récupérés de la base de données.
269
+ Retourne une liste vide en cas d'erreur.
270
+ """
271
+ # Connexion à la base de données SQLite
272
  conn = get_db_connection()
273
+
274
+ # Si la connexion échoue, retourner une liste vide
275
+ if conn is None:
276
+ return []
277
+
278
  cursor = conn.cursor()
279
+
280
  try:
281
+ # Requête SQL pour récupérer la colonne 'ingredients' de la table 'liste_courses'
282
  cursor.execute("SELECT ingredients FROM liste_courses")
283
+
284
+ # Récupère tous les résultats de la requête
285
+ ingredients_list = cursor.fetchall()
286
+
287
+ # Retourne la liste des ingrédients (sous forme de liste de tuples)
288
+ return [ingredient[0] for ingredient in ingredients_list]
289
+
290
+ except sqlite3.Error as e:
291
+ # En cas d'erreur, on enregistre l'erreur dans les logs et on retourne une liste vide
292
+ logger.error(f"Erreur lors de la récupération des ingrédients : {e}")
293
+ return []
294
+
295
  finally:
296
+ # Fermeture du curseur et de la connexion à la base de données
297
  cursor.close()
298
  conn.close()
299
 
300
+
301
  # Fonction pour récupérer le coût total des requêtes
302
+ # def get_total_cost():
303
+ # conn = get_db_connection()
304
+ # if conn is None:
305
+ # return 0.0
306
+ # try:
307
+ # cursor = conn.cursor()
308
+ # cursor.execute("SELECT SUM(total_cout) FROM messages WHERE total_cout IS NOT NULL")
309
+ # result = cursor.fetchone()
310
+ # return round(result[0], 2) if result[0] is not None else 0.0
311
+ # except Exception as e:
312
+ # logger.error(f"Erreur lors de la récupération du coût total : {e}")
313
+ # return 0.0
314
+ # finally:
315
+ # cursor.close()
316
+ # conn.close()
317
+
318
+
319
+ def get_total_cost() -> float:
320
+ """
321
+ Récupère le coût total des messages dans la table `messages` de la base de données SQLite.
322
+
323
+ Cette fonction se connecte à la base de données SQLite, exécute une requête SQL pour récupérer la somme des valeurs
324
+ présentes dans la colonne `total_cout` de la table `messages`, et retourne le total arrondi à 2 décimales.
325
+
326
+ Returns:
327
+ float: Le coût total des messages. Retourne 0.0 si aucune donnée n'est disponible ou en cas d'erreur.
328
+ """
329
+ # Connexion à la base de données SQLite
330
  conn = get_db_connection()
331
+
332
+ # Si la connexion échoue, retourner 0.0
333
  if conn is None:
334
  return 0.0
335
+
336
+ cursor = conn.cursor()
337
+
338
  try:
339
+ # Requête SQL pour récupérer la somme de la colonne 'total_cout' de la table 'messages'
340
+ cursor.execute(
341
+ "SELECT SUM(total_cout) FROM messages WHERE total_cout IS NOT NULL"
342
+ )
343
+
344
+ # Récupère le résultat de la requête
345
  result = cursor.fetchone()
346
+
347
+ # Retourne la somme arrondie à 2 décimales si un résultat est trouvé, sinon retourne 0.0
348
  return round(result[0], 2) if result[0] is not None else 0.0
349
+
350
+ except sqlite3.Error as e:
351
+ # En cas d'erreur, on log l'erreur et on retourne 0.0
352
  logger.error(f"Erreur lors de la récupération du coût total : {e}")
353
  return 0.0
354
+
355
  finally:
356
+ # Fermeture du curseur et de la connexion à la base de données
357
  cursor.close()
358
  conn.close()
359
 
360
+
361
  # Fonction pour récupérer l'impact écologique estimé
362
+ # def get_total_impact():
363
+ # conn = get_db_connection()
364
+ # if conn is None:
365
+ # return 0.0
366
+ # try:
367
+ # cursor = conn.cursor()
368
+ # cursor.execute("SELECT SUM(impact_eco) FROM messages WHERE impact_eco IS NOT NULL")
369
+ # result = cursor.fetchone()
370
+ # return round(result[0], 2) if result[0] is not None else 0.0
371
+ # except Exception as e:
372
+ # logger.error(f"Erreur lors de la récupération de l'impact écologique : {e}")
373
+ # return 0.0
374
+ # finally:
375
+ # cursor.close()
376
+ # conn.close()
377
+
378
+
379
+ def get_total_impact() -> float:
380
+ """
381
+ Récupère l'impact écologique total des messages dans la table `messages` de la base de données SQLite.
382
+
383
+ Cette fonction se connecte à la base de données SQLite, exécute une requête SQL pour récupérer la somme des valeurs
384
+ présentes dans la colonne `impact_eco` de la table `messages`, et retourne le total arrondi à 2 décimales.
385
+
386
+ Returns:
387
+ float: L'impact écologique total des messages. Retourne 0.0 si aucune donnée n'est disponible ou en cas d'erreur.
388
+ """
389
+ # Connexion à la base de données SQLite
390
  conn = get_db_connection()
391
+
392
+ # Si la connexion échoue, retourner 0.0
393
  if conn is None:
394
  return 0.0
395
+
396
+ cursor = conn.cursor()
397
+
398
  try:
399
+ # Requête SQL pour récupérer la somme de la colonne 'impact_eco' de la table 'messages'
400
+ cursor.execute(
401
+ "SELECT SUM(impact_eco) FROM messages WHERE impact_eco IS NOT NULL"
402
+ )
403
+
404
+ # Récupère le résultat de la requête
405
  result = cursor.fetchone()
406
+
407
+ # Retourne la somme arrondie à 2 décimales si un résultat est trouvé, sinon retourne 0.0
408
  return round(result[0], 2) if result[0] is not None else 0.0
409
+
410
+ except sqlite3.Error as e:
411
+ # En cas d'erreur, on log l'erreur et on retourne 0.0
412
  logger.error(f"Erreur lors de la récupération de l'impact écologique : {e}")
413
  return 0.0
414
+
415
  finally:
416
+ # Fermeture du curseur et de la connexion à la base de données
417
  cursor.close()
418
  conn.close()
server/db/dbmanager.py CHANGED
@@ -1,5 +1,6 @@
1
  import streamlit as st
2
  import psycopg2
 
3
  from psycopg2 import extras
4
  from datetime import datetime
5
  import logging
@@ -7,32 +8,34 @@ import json
7
  import pandas as pd
8
  from typing import List, Dict, Tuple
9
  import os
10
- import sys
11
 
12
  # Configuration du logging
13
  logging.basicConfig(level=logging.INFO, handlers=[logging.StreamHandler()])
14
  logger = logging.getLogger(__name__)
15
 
16
- sys.stdout.reconfigure(encoding='utf-8')
17
 
18
  # Configuration de la base de données
19
  db_config = {
20
  "database": st.secrets["DB_NAME"],
21
- "user": st.secrets["DB_USER"],
22
- "password": st.secrets["DB_PASSWORD"],
23
- "host": st.secrets["DB_HOST"],
24
- "port": st.secrets["DB_PORT"]
25
  }
26
 
27
  ######################### CLASSES #########################
28
 
 
29
  class DBManager:
30
- def __init__(self, db_config: Dict, schema_file: str):
31
  """
32
  Initialise la connexion à la base PostgreSQL et charge le schéma.
33
-
34
- :param db_config: Dictionnaire avec les informations de connexion (host, database, user, password).
35
- :param schema_file: Chemin vers le fichier JSON contenant le schéma de la base.
 
36
  """
37
 
38
  self.db_config = db_config
@@ -42,134 +45,293 @@ class DBManager:
42
  self._load_schema()
43
  self._connect_to_database()
44
  self._create_database()
45
- self.cursor.execute("SET NAMES 'UTF8'")
46
 
47
-
48
-
49
- def _load_schema(self):
50
- """Charge le schéma de base de données depuis un fichier JSON."""
51
  if not os.path.exists(self.schema_file):
52
  raise FileNotFoundError(f"Fichier non trouvé : {self.schema_file}")
53
-
54
  with open(self.schema_file, "r", encoding="utf-8") as file:
55
  self.schema = json.load(file)
56
 
57
- def _connect_to_database(self):
58
- """Établit une connexion avec la base PostgreSQL."""
 
 
 
 
 
 
 
 
 
 
 
59
  try:
60
- self.connection = psycopg2.connect(**self.db_config, cursor_factory=extras.DictCursor)
 
 
 
 
 
 
61
  self.cursor = self.connection.cursor()
62
- except psycopg2.Error as err:
63
  logger.error(f"Erreur de connexion : {err}")
64
  return
65
 
66
- def _create_database(self):
67
- """Crée les tables définies dans le schéma JSON."""
68
- for table_name, table_info in self.schema['tables'].items():
69
- create_table_query = self._generate_create_table_query(table_name, table_info['columns'])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  try:
71
  self.cursor.execute(create_table_query)
72
- except psycopg2.Error as err:
73
- logger.error(f"Erreur de connexion : {err}")
 
 
74
  return
75
  finally:
76
  self.connection.commit()
77
 
 
 
 
 
 
 
 
 
 
 
 
78
  def _generate_create_table_query(self, table_name: str, columns: List[Dict]) -> str:
79
- """Génère une requête SQL pour créer une table en fonction du schéma."""
 
 
 
 
 
 
 
 
 
 
80
  column_definitions = []
81
  for column in columns:
82
  column_definition = f"{column['name']} {column['type']}"
83
- if 'constraints' in column and column['constraints']:
84
- column_definition += " " + " ".join(column['constraints'])
 
 
 
 
 
 
 
 
85
  column_definitions.append(column_definition)
 
86
  columns_str = ", ".join(column_definitions)
87
  return f"CREATE TABLE IF NOT EXISTS {table_name} ({columns_str});"
88
 
89
- def insert_data_from_dict(self, table_name: str, data: List[Dict], id_column: str) -> List[int]:
90
- """Insère des données dans une table à partir d'une liste de dictionnaires et retourne les IDs insérés.
91
-
92
- table_name : str : nom de la table
93
- data : List[Dict] : données à insérer
94
- id_column : str : nom de la colonne d'ID à retourner
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  """
96
- columns = ", ".join(data[0].keys())
97
- placeholders = ", ".join(['%s' for _ in data[0].keys()])
98
 
99
- # Requête pour insérer les données et retourner l'ID dynamique
100
- query = f"INSERT INTO {table_name} ({columns}) VALUES ({placeholders}) RETURNING {id_column}"
 
101
 
102
- ids = []
 
 
 
 
 
 
 
 
 
 
 
103
  try:
104
  for row in data:
105
  self.cursor.execute(query, tuple(row.values()))
106
- inserted_id = self.cursor.fetchone()[0]
 
 
107
  ids.append(inserted_id)
108
  return ids
109
- except psycopg2.Error as err:
110
- logger.error(f"Erreur de connexion : {err}")
 
 
111
  return
112
  finally:
113
  self.connection.commit()
114
 
115
- def insert_data_from_dict(self, table_name: str, data: List[Dict], id_column: str) -> List[int]:
116
- """Insère des données dans une table à partir d'une liste de dictionnaires et retourne les IDs insérés.
117
-
118
- table_name : str : nom de la table
119
- data : List[Dict] : données à insérer
120
- id_column : str : nom de la colonne d'ID à retourner
121
- """
122
- columns = ", ".join(data[0].keys())
123
- placeholders = ", ".join(['%s' for _ in data[0].keys()])
124
-
125
- # Requête pour insérer les données et retourner l'ID dynamique
126
- query = f"INSERT INTO {table_name} ({columns}) VALUES ({placeholders}) RETURNING {id_column}"
127
-
128
- ids = [] # Liste pour stocker les IDs retournés
129
- for row in data:
130
- self.cursor.execute(query, tuple(row.values()))
131
- inserted_id = self.cursor.fetchone()[0] # Récupère le premier (et unique) élément de la ligne retournée
132
- ids.append(inserted_id)
133
-
134
- self.connection.commit()
135
- return ids
136
-
137
 
 
 
 
 
 
 
 
 
138
 
139
  def insert_data_from_csv(self, table_name: str, csv_file: str) -> None:
140
- """Insère des données dans une table à partir d'un fichier CSV."""
 
 
 
 
 
 
141
  df = pd.read_csv(csv_file)
142
  columns = df.columns.tolist()
143
- placeholders = ", ".join(['%s' for _ in columns])
144
- query = f"INSERT INTO {table_name} ({', '.join(columns)}) VALUES ({placeholders})"
145
-
 
 
 
 
146
  try:
147
  for row in df.itertuples(index=False, name=None):
148
- self.cursor.execute(query, row)
149
- except psycopg2.Error as err:
150
- logger.error(f"Erreur de connexion : {err}")
151
- return
 
 
 
152
  finally:
153
  self.connection.commit()
154
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  def fetch_all(self, table_name: str) -> List[Tuple]:
156
- """Récupère toutes les données d'une table."""
 
 
 
 
 
 
 
 
157
  try:
158
  self.cursor.execute(f"SELECT * FROM {table_name}")
159
  return self.cursor.fetchall()
160
- except psycopg2.Error as err:
161
- logger.error(f"Erreur de connexion : {err}")
162
- return
163
-
164
-
165
- def execute_safe(self, query: str, params: Tuple = (), fetch: bool = False):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  """
167
  Exécute une requête SQL avec gestion centralisée des erreurs.
168
 
169
- :param query: Requête SQL à exécuter.
170
- :param params: Paramètres de la requête.
171
- :param fetch: Indique si les résultats doivent être récupérés.
172
- :return: Résultats de la requête (si fetch est True), sinon None.
 
 
 
173
  """
174
  try:
175
  self.cursor.execute(query, params)
@@ -177,300 +339,715 @@ class DBManager:
177
  return self.cursor.fetchall() # Retourner les résultats si demandé
178
  else:
179
  self.connection.commit() # Valider les modifications
180
- except psycopg2.Error as err:
181
- logger.error(f"Erreur de connexion : {err}")
182
  self.connection.rollback() # Annuler la transaction en cas d'erreur
183
- raise RuntimeError(f"Erreur SQL : {err} | Query : {query} | Params : {params}")
 
 
184
 
 
 
 
 
 
 
 
 
 
185
 
186
- def fetch_by_condition(self, table_name: str, condition: str, params: Tuple = ()) -> List[Tuple]:
187
- """Récupère les données d'une table avec une condition."""
 
 
 
 
 
 
 
 
 
 
 
 
188
  query = f"SELECT * FROM {table_name} WHERE {condition}"
189
  try:
190
- self.cursor.execute(query, params)
191
  return self.execute_safe(query, params, fetch=True)
192
- except psycopg2.Error as err:
193
- logger.error(f"Erreur de connexion : {err}")
194
- return
195
-
 
 
 
 
 
 
 
 
 
 
 
 
196
 
197
- def update_data(self, table_name: str, set_clause: str, condition: str, params: Tuple) -> None:
198
- """Met à jour des données dans une table."""
 
 
 
 
 
 
 
 
 
 
199
  query = f"UPDATE {table_name} SET {set_clause} WHERE {condition}"
200
  try:
201
  self.cursor.execute(query, params)
202
- except psycopg2.Error as err:
203
- logger.error(f"Erreur de connexion : {err}")
204
- return
 
205
  finally:
206
- self.connection.commit()
 
 
 
 
 
 
 
 
 
 
 
207
 
208
  def delete_data(self, table_name: str, condition: str, params: Tuple) -> None:
209
- """Supprime des données d'une table selon une condition."""
 
 
 
 
 
 
 
210
  query = f"DELETE FROM {table_name} WHERE {condition}"
211
  try:
212
  self.cursor.execute(query, params)
213
- except psycopg2.Error as err:
214
- logger.error(f"Erreur de connexion : {err}")
215
- return
 
216
  finally:
217
- self.connection.commit()
 
 
 
 
 
 
218
 
219
  def close_connection(self) -> None:
220
- """Ferme la connexion à la base de données."""
 
 
221
  if self.connection:
222
- self.cursor.close()
223
- self.connection.close()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
 
225
  def create_index(self, table_name: str, column_name: str) -> None:
226
- """Crée un index sur une colonne spécifique pour améliorer les performances de recherche."""
 
 
227
  query = f"CREATE INDEX IF NOT EXISTS idx_{table_name}_{column_name} ON {table_name} ({column_name})"
228
  try:
229
  self.cursor.execute(query)
230
- except psycopg2.Error as err:
231
- logger.error(f"Erreur de connexion : {err}")
232
- return
 
233
  finally:
234
- self.connection.commit()
 
 
 
 
 
 
 
 
 
235
 
236
  def select(self, query: str, params: Tuple = ()) -> List[Tuple]:
237
- """Exécute une requête SELECT personnalisée et retourne les résultats."""
 
 
 
 
 
 
 
 
 
238
  try:
239
  self.cursor.execute(query, params)
240
  return self.cursor.fetchall()
241
- except psycopg2.Error as err:
242
- logger.error(f"Erreur de connexion : {err}")
243
- return
244
-
245
- def query(self, query, params=None):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
246
  """
247
- Exécute une requête SQL, en utilisant les paramètres fournis,
248
- et retourne les résultats si nécessaire.
 
 
 
 
 
 
249
  """
250
  try:
251
- self.cursor.execute(query, params)
252
- except psycopg2.Error as err:
253
- logger.error(f"Erreur de connexion : {err}")
 
 
 
254
  return
255
  finally:
256
  # Si la requête est un SELECT, récupérer les résultats
257
  if query.strip().upper().startswith("SELECT"):
258
  return self.cursor.fetchall()
259
- else: # Si ce n'est pas un SELECT, ne rien retourner (utile pour INSERT/UPDATE)
260
  self.connection.commit()
261
- return None
262
-
263
 
264
 
265
-
266
-
267
  ######################### FONCTIONS #########################
268
 
269
  # Mettre DBManager en cache
270
  @st.cache_resource
271
  def get_db_manager():
272
- return DBManager(db_config, os.path.join("server","db","schema.json"))
 
273
 
 
 
 
274
 
275
- def save_message(db_manager, id_conversation: int, role: str, content: str,temps_traitement, total_cout, impact_eco) -> None:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
  """
277
  Sauvegarde un message dans la base de données, en associant l'utilisateur à la conversation.
278
 
279
- :param db_manager: Instance de DBManager.
280
- :param id_conversation: ID de la conversation associée.
281
- :param role: Rôle de l'intervenant (ex. 'user' ou 'assistant').
282
- :param content: Contenu du message.
 
 
 
 
283
  """
284
  timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
285
- data = [{
286
- "id_conversation": id_conversation,
287
- "role": role,
288
- "content": content,
289
- "timestamp": timestamp,
290
- "temps_traitement":temps_traitement,
291
- "total_cout": total_cout,
292
- "impact_eco": impact_eco
293
- }]
 
 
 
294
  try:
295
- db_manager.insert_data_from_dict("messages", data, id_column="id_message")
296
- except psycopg2.Error as err:
297
- logger.error(f"Erreur de connexion: {err}")
298
- return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
 
300
  def create_conversation(db_manager, title: str, id_utilisateur: int) -> int:
301
  """
302
  Crée une nouvelle conversation dans la base de données, en associant l'utilisateur à la conversation.
303
 
304
- :param db_manager: Instance de DBManager.
305
- :param title: Titre de la conversation.
306
- :param id_utilisateur: ID de l'utilisateur associé.
307
- :return: ID de la conversation nouvellement créée.
 
 
 
 
308
  """
309
  created_at = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
310
- data = [{
311
- "created_at": created_at,
312
- "title": title,
313
- "id_utilisateur": id_utilisateur,
314
- }]
315
  try:
316
- result = db_manager.insert_data_from_dict("conversations", data, id_column="id_conversation")
317
- return result[0]
318
- except psycopg2.Error as err:
319
- logger.error(f"Erreur de connexion : {err}")
320
- return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
321
 
322
  def load_messages(db_manager, id_conversation: int) -> List[Dict]:
323
  """
324
  Charge les messages associés à une conversation.
325
 
326
- :param db_manager: Instance de DBManager.
327
- :param id_conversation: ID de la conversation.
328
- :return: Liste des messages sous forme de dictionnaires.
 
 
329
  """
330
  query = """
331
- SELECT *
332
  FROM messages
333
- WHERE id_conversation = %s
334
  ORDER BY timestamp ASC
335
  """
336
  try:
337
  result = db_manager.query(query, (id_conversation,))
338
- return [{"role": row["role"], "content": row["content"], "timestamp":row["timestamp"], "temps_traitement":row["temps_traitement"]} for row in result]
339
- except psycopg2.Error as err:
340
- logger.error(f"Erreur de connexion : {err}")
341
- return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
342
 
343
  def load_conversations(db_manager, id_utilisateur: int) -> List[Dict]:
344
  """
345
  Charge toutes les conversations enregistrées pour un utilisateur donné.
346
 
347
- :param db_manager: Instance de DBManager.
348
- :param id_utilisateur: ID de l'utilisateur.
349
- :return: Liste des conversations.
 
 
350
  """
351
  query = """
352
- SELECT * FROM conversations
353
- WHERE id_utilisateur = %s
 
354
  ORDER BY created_at DESC
355
  """
356
  try:
357
  result = db_manager.query(query, (id_utilisateur,))
358
-
359
-
360
  return [
361
- {"id_conversation": row["id_conversation"], "created_at": row["created_at"], "title": row["title"]} for row in result
 
 
 
 
 
362
  ]
363
- except psycopg2.Error as err:
364
- logger.error(f"Erreur de connexion : {err}")
365
- return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
366
 
367
  def update_conversation(db_manager, id_conversation: int, id_utilisateur: int) -> None:
368
  """
369
  Met à jour le champ `created_at` d'une conversation donnée pour un utilisateur.
370
 
371
- :param db_manager: Instance de DBManager.
372
- :param id_conversation: ID de la conversation à mettre à jour.
373
- :param id_utilisateur: ID de l'utilisateur.
 
374
  """
375
  new_timer = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
376
  query = """
377
  UPDATE conversations
378
- SET created_at = %s
379
- WHERE id_conversation = %s AND id_utilisateur = %s
380
  """
381
  try:
382
  db_manager.query(query, (new_timer, id_conversation, id_utilisateur))
383
- except psycopg2.Error as err:
384
- logger.error(f"Erreur de connexion : {err}")
385
  return
386
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
387
  def update_conversation_title(db_manager, id_conversation: int, new_title: str) -> None:
388
  """
389
  Met à jour le titre d'une conversation si celui-ci est encore "Nouvelle conversation".
390
 
391
- :param db_manager: Instance de DBManager.
392
- :param id_conversation: ID de la conversation à mettre à jour.
393
- :param new_title: Nouveau titre de la conversation.
 
394
  """
395
  query = """
396
  UPDATE conversations
397
- SET title = %s
398
- WHERE id_conversation = %s AND title = 'Nouvelle conversation'
399
  """
400
  try:
401
  db_manager.query(query, (new_title, id_conversation))
402
- except psycopg2.Error as err:
403
- logger.error(f"Erreur de connexion : {err}")
 
 
404
  return
405
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
406
  def get_conversation_title(db_manager, id_conversation: int) -> str:
407
  """
408
  Récupère le titre d'une conversation spécifique en utilisant `fetch_by_condition`.
409
 
410
- :param db_manager: Instance de DBManager.
411
- :param id_conversation: ID de la conversation à interroger.
412
- :return: Le titre de la conversation ou "Nouvelle conversation".
 
 
 
413
  """
414
  table_name = "conversations"
415
- condition = "id_conversation = %s"
416
  try:
417
- results = db_manager.fetch_by_condition(table_name, condition, (id_conversation,))
 
 
418
  if results:
419
- # Suppose que `title` est la troisième colonne
420
- return results[0][2]
 
 
421
  return "Nouvelle conversation"
422
- except psycopg2.Error as err:
423
- logger.error(f"Erreur de connexion : {err}")
424
- return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
425
 
426
  def delete_conversation(db_manager, id_conversation: int) -> None:
427
  """
428
  Supprime une conversation et ses messages associés de la base de données.
429
 
430
- :param db_manager: Instance de DBManager.
431
- :param id_conversation: ID de la conversation à supprimer.
 
432
  """
433
  try:
434
  # Supprimer les messages liés à la conversation
435
- query_delete_messages = "DELETE FROM messages WHERE id_conversation = %s"
436
  db_manager.query(query_delete_messages, (id_conversation,))
437
 
438
  # Supprimer la conversation elle-même
439
- query_delete_conversation = "DELETE FROM conversations WHERE id_conversation = %s"
 
 
440
  db_manager.query(query_delete_conversation, (id_conversation,))
441
 
442
- print(f"✅ Conversation {id_conversation} supprimée avec succès.")
443
- except Exception as e:
444
- print(f"❌ Erreur lors de la suppression de la conversation {id_conversation}: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
445
 
446
- def load_chatbot_suggestions(db_manager, user_id):
 
447
  """
448
  Charge les suggestions du chatbot enregistrées pour un utilisateur donné.
 
 
 
 
 
 
 
449
  """
450
- query = "SELECT repas_suggestion FROM suggestions_repas WHERE id_utilisateur = %s"
451
  try:
452
  db_manager.cursor.execute(query, (user_id,))
453
  suggestions = [row[0] for row in db_manager.cursor.fetchall()]
454
  return suggestions
455
- except psycopg2.Error as err:
456
  logger.error(f"Erreur lors du chargement des suggestions : {err}")
457
  return []
458
 
459
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
460
 
461
- def save_chatbot_suggestions(db_manager, user_id, suggestions):
 
462
  """
463
  Sauvegarde les suggestions du chatbot dans la base de données.
 
 
 
 
 
 
464
  """
465
  query = """
466
  INSERT INTO suggestions_repas (id_utilisateur, repas_suggestion, motif_suggestion)
467
- VALUES (%s, %s, %s)
468
  """
469
  try:
470
  for suggestion in suggestions:
471
  db_manager.cursor.execute(query, (user_id, suggestion, "Chatbot"))
472
  db_manager.connection.commit()
473
- except psycopg2.Error as err:
474
  logger.error(f"Erreur lors de l'enregistrement des suggestions : {err}")
475
-
476
-
 
1
  import streamlit as st
2
  import psycopg2
3
+ import sqlite3
4
  from psycopg2 import extras
5
  from datetime import datetime
6
  import logging
 
8
  import pandas as pd
9
  from typing import List, Dict, Tuple
10
  import os
11
+ import sys
12
 
13
  # Configuration du logging
14
  logging.basicConfig(level=logging.INFO, handlers=[logging.StreamHandler()])
15
  logger = logging.getLogger(__name__)
16
 
17
+ sys.stdout.reconfigure(encoding="utf-8")
18
 
19
  # Configuration de la base de données
20
  db_config = {
21
  "database": st.secrets["DB_NAME"],
22
+ # "user": st.secrets["DB_USER"],
23
+ # "password": st.secrets["DB_PASSWORD"],
24
+ # "host": st.secrets["DB_HOST"],
25
+ # "port": st.secrets["DB_PORT"]
26
  }
27
 
28
  ######################### CLASSES #########################
29
 
30
+
31
  class DBManager:
32
+ def __init__(self, db_config: Dict, schema_file: str) -> None:
33
  """
34
  Initialise la connexion à la base PostgreSQL et charge le schéma.
35
+
36
+ Args:
37
+ db_config (Dict) : dictionnaire avec les informations de connexion (host, database, user, password).
38
+ schema_file (str) : chemin vers le fichier JSON contenant le schéma de la base.
39
  """
40
 
41
  self.db_config = db_config
 
45
  self._load_schema()
46
  self._connect_to_database()
47
  self._create_database()
48
+ # self.cursor.execute("SET NAMES 'UTF8'")
49
 
50
+ def _load_schema(self) -> None:
51
+ """
52
+ Charge le schéma de base de données depuis un fichier JSON.
53
+ """
54
  if not os.path.exists(self.schema_file):
55
  raise FileNotFoundError(f"Fichier non trouvé : {self.schema_file}")
56
+
57
  with open(self.schema_file, "r", encoding="utf-8") as file:
58
  self.schema = json.load(file)
59
 
60
+ # def _connect_to_database(self):
61
+ # """Établit une connexion avec la base PostgreSQL."""
62
+ # try:
63
+ # self.connection = psycopg2.connect(**self.db_config, cursor_factory=extras.DictCursor)
64
+ # self.cursor = self.connection.cursor()
65
+ # except psycopg2.Error as err:
66
+ # logger.error(f"Erreur de connexion : {err}")
67
+ # return
68
+
69
+ def _connect_to_database(self) -> None:
70
+ """
71
+ Établit une connexion avec la base SQLite.
72
+ """
73
  try:
74
+ # Connexion à la base de données SQLite
75
+ self.connection = sqlite3.connect(
76
+ self.db_config["database"], check_same_thread=False
77
+ )
78
+ self.connection.row_factory = (
79
+ sqlite3.Row
80
+ ) # Pour des résultats sous forme de dictionnaire
81
  self.cursor = self.connection.cursor()
82
+ except sqlite3.Error as err:
83
  logger.error(f"Erreur de connexion : {err}")
84
  return
85
 
86
+ # def _create_database(self):
87
+ # """Crée les tables définies dans le schéma JSON."""
88
+ # for table_name, table_info in self.schema['tables'].items():
89
+ # create_table_query = self._generate_create_table_query(table_name, table_info['columns'])
90
+ # try:
91
+ # self.cursor.execute(create_table_query)
92
+ # except psycopg2.Error as err:
93
+ # logger.error(f"Erreur de connexion : {err}")
94
+ # return
95
+ # finally:
96
+ # self.connection.commit()
97
+
98
+ import sqlite3
99
+
100
+ def _create_database(self) -> None:
101
+ """
102
+ Crée les tables définies dans le schéma JSON.
103
+ """
104
+ for table_name, table_info in self.schema["tables"].items():
105
+ create_table_query = self._generate_create_table_query(
106
+ table_name, table_info["columns"]
107
+ )
108
  try:
109
  self.cursor.execute(create_table_query)
110
+ except sqlite3.Error as err:
111
+ logger.error(
112
+ f"Erreur lors de la création de la table {table_name}: {err}"
113
+ )
114
  return
115
  finally:
116
  self.connection.commit()
117
 
118
+ # def _generate_create_table_query(self, table_name: str, columns: List[Dict]) -> str:
119
+ # """Génère une requête SQL pour créer une table en fonction du schéma."""
120
+ # column_definitions = []
121
+ # for column in columns:
122
+ # column_definition = f"{column['name']} {column['type']}"
123
+ # if 'constraints' in column and column['constraints']:
124
+ # column_definition += " " + " ".join(column['constraints'])
125
+ # column_definitions.append(column_definition)
126
+ # columns_str = ", ".join(column_definitions)
127
+ # return f"CREATE TABLE IF NOT EXISTS {table_name} ({columns_str});"
128
+
129
  def _generate_create_table_query(self, table_name: str, columns: List[Dict]) -> str:
130
+ """
131
+ Génère une requête SQL pour créer une table en fonction du schéma.
132
+
133
+ Args:
134
+ table_name (str): nom de la table.
135
+ columns (List[Dict]): colonnes de la table à créer.
136
+
137
+ Returns:
138
+ str : la requête SQL CREATE TABLE paramétrée.
139
+
140
+ """
141
  column_definitions = []
142
  for column in columns:
143
  column_definition = f"{column['name']} {column['type']}"
144
+
145
+ # Conversion quand le type n'est pas compatible avec SQLite (ex. : SERIAL -> INTEGER PRIMARY KEY AUTOINCREMENT)
146
+ if column["type"] == "SERIAL":
147
+ column_definition = (
148
+ f"{column['name']} INTEGER PRIMARY KEY AUTOINCREMENT"
149
+ )
150
+
151
+ if "constraints" in column and column["constraints"]:
152
+ column_definition += " " + " ".join(column["constraints"])
153
+
154
  column_definitions.append(column_definition)
155
+
156
  columns_str = ", ".join(column_definitions)
157
  return f"CREATE TABLE IF NOT EXISTS {table_name} ({columns_str});"
158
 
159
+ # def insert_data_from_dict(self, table_name: str, data: List[Dict], id_column: str) -> List[int]:
160
+ # """Insère des données dans une table à partir d'une liste de dictionnaires et retourne les IDs insérés.
161
+
162
+ # table_name : str : nom de la table
163
+ # data : List[Dict] : données à insérer
164
+ # id_column : str : nom de la colonne d'ID à retourner
165
+ # """
166
+ # columns = ", ".join(data[0].keys())
167
+ # placeholders = ", ".join(['%s' for _ in data[0].keys()])
168
+
169
+ # # Requête pour insérer les données et retourner l'ID dynamique
170
+ # query = f"INSERT INTO {table_name} ({columns}) VALUES ({placeholders}) RETURNING {id_column}"
171
+
172
+ # ids = []
173
+ # try:
174
+ # for row in data:
175
+ # self.cursor.execute(query, tuple(row.values()))
176
+ # inserted_id = self.cursor.fetchone()[0]
177
+ # ids.append(inserted_id)
178
+ # return ids
179
+ # except psycopg2.Error as err:
180
+ # logger.error(f"Erreur de connexion : {err}")
181
+ # return
182
+ # finally:
183
+ # self.connection.commit()
184
+
185
+ def insert_data_from_dict(self, table_name: str, data: List[Dict]) -> List[int]:
186
  """
187
+ Insère des données dans une table à partir d'une liste de dictionnaires et retourne les IDs insérés.
 
188
 
189
+ Args:
190
+ table_name (str): nom de la table.
191
+ data (List[Dict]): données à insérer.
192
 
193
+ Returns:
194
+ List[int]: liste des ID des données insérées.
195
+ """
196
+ columns = ", ".join(data[0].keys())
197
+ placeholders = ", ".join(
198
+ ["?" for _ in data[0].keys()]
199
+ ) # SQLite utilise '?' pour les placeholders
200
+
201
+ # Requête pour insérer les données
202
+ query = f"INSERT INTO {table_name} ({columns}) VALUES ({placeholders})"
203
+
204
+ ids = []
205
  try:
206
  for row in data:
207
  self.cursor.execute(query, tuple(row.values()))
208
+ inserted_id = (
209
+ self.cursor.lastrowid
210
+ ) # Récupère l'ID du dernier enregistrement inséré
211
  ids.append(inserted_id)
212
  return ids
213
+ except sqlite3.Error as err:
214
+ logger.error(
215
+ f"Erreur lors de l'insertion des données dans {table_name}: {err}"
216
+ )
217
  return
218
  finally:
219
  self.connection.commit()
220
 
221
+ # def insert_data_from_csv(self, table_name: str, csv_file: str) -> None:
222
+ # """Insère des données dans une table à partir d'un fichier CSV."""
223
+ # df = pd.read_csv(csv_file)
224
+ # columns = df.columns.tolist()
225
+ # placeholders = ", ".join(['%s' for _ in columns])
226
+ # query = f"INSERT INTO {table_name} ({', '.join(columns)}) VALUES ({placeholders})"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
 
228
+ # try:
229
+ # for row in df.itertuples(index=False, name=None):
230
+ # self.cursor.execute(query, row)
231
+ # except psycopg2.Error as err:
232
+ # logger.error(f"Erreur de connexion : {err}")
233
+ # return
234
+ # finally:
235
+ # self.connection.commit()
236
 
237
  def insert_data_from_csv(self, table_name: str, csv_file: str) -> None:
238
+ """
239
+ Insère des données dans une table à partir d'un fichier CSV.
240
+
241
+ Args:
242
+ table_name (str): nom de la table dans laquelle insérer des données.
243
+ csv_file (str): lien du fichier csv contenant les données.
244
+ """
245
  df = pd.read_csv(csv_file)
246
  columns = df.columns.tolist()
247
+ placeholders = ", ".join(
248
+ ["?" for _ in columns]
249
+ ) # Utilisation de '?' pour SQLite
250
+ query = (
251
+ f"INSERT INTO {table_name} ({', '.join(columns)}) VALUES ({placeholders})"
252
+ )
253
+
254
  try:
255
  for row in df.itertuples(index=False, name=None):
256
+ self.cursor.execute(
257
+ query, row
258
+ ) # Exécution de la requête avec les valeurs du CSV
259
+ except sqlite3.Error as err:
260
+ logger.error(
261
+ f"Erreur lors de l'insertion des données depuis {csv_file} : {err}"
262
+ )
263
  finally:
264
  self.connection.commit()
265
 
266
+ # def fetch_all(self, table_name: str) -> List[Tuple]:
267
+ # """
268
+ # Récupère toutes les données d'une table.
269
+
270
+ # Args:
271
+ # table_name (str): nom de la table de laquelle récupérer des données.
272
+
273
+ # Returns:
274
+ # List[Tuple]: liste des enregistrements récupérés à partir de la table.
275
+ # """
276
+ # try:
277
+ # self.cursor.execute(f"SELECT * FROM {table_name}")
278
+ # return self.cursor.fetchall()
279
+ # except sqlite3.Error as err:
280
+ # logger.error(f"Erreur de connexion : {err}")
281
+ # return
282
+
283
  def fetch_all(self, table_name: str) -> List[Tuple]:
284
+ """
285
+ Récupère toutes les données d'une table.
286
+
287
+ Args:
288
+ table_name (str): nom de la table de laquelle récupérer des données.
289
+
290
+ Returns:
291
+ List[Tuple]: liste des enregistrements récupérés à partir de la table.
292
+ """
293
  try:
294
  self.cursor.execute(f"SELECT * FROM {table_name}")
295
  return self.cursor.fetchall()
296
+ except sqlite3.Error as err:
297
+ logger.error(
298
+ f"Erreur lors de la récupération des données de la table {table_name} : {err}"
299
+ )
300
+ return [] # Retourne une liste vide en cas d'erreur
301
+
302
+ # def execute_safe(self, query: str, params: Tuple = (), fetch: bool = False):
303
+ # """
304
+ # Exécute une requête SQL avec gestion centralisée des erreurs.
305
+
306
+ # :param query: Requête SQL à exécuter.
307
+ # :param params: Paramètres de la requête.
308
+ # :param fetch: Indique si les résultats doivent être récupérés.
309
+ # :return: Résultats de la requête (si fetch est True), sinon None.
310
+ # """
311
+ # try:
312
+ # self.cursor.execute(query, params)
313
+ # if fetch:
314
+ # return self.cursor.fetchall() # Retourner les résultats si demandé
315
+ # else:
316
+ # self.connection.commit() # Valider les modifications
317
+ # except psycopg2.Error as err:
318
+ # logger.error(f"Erreur de connexion : {err}")
319
+ # self.connection.rollback() # Annuler la transaction en cas d'erreur
320
+ # raise RuntimeError(f"Erreur SQL : {err} | Query : {query} | Params : {params}")
321
+
322
+ def execute_safe(
323
+ self, query: str, params: Tuple = (), fetch: bool = False
324
+ ) -> List[Tuple]:
325
  """
326
  Exécute une requête SQL avec gestion centralisée des erreurs.
327
 
328
+ Args:
329
+ query (str): requête SQL à exécuter.
330
+ params (Tuple): paramètres de la requête.
331
+ fetch (bool): indique si les résultats doivent être récupérés.
332
+
333
+ Returns:
334
+ List[Tuple]: résultats de la requête (si fetch est True), sinon None.
335
  """
336
  try:
337
  self.cursor.execute(query, params)
 
339
  return self.cursor.fetchall() # Retourner les résultats si demandé
340
  else:
341
  self.connection.commit() # Valider les modifications
342
+ except sqlite3.Error as err:
343
+ logger.error(f"Erreur lors de l'exécution de la requête : {err}")
344
  self.connection.rollback() # Annuler la transaction en cas d'erreur
345
+ raise RuntimeError(
346
+ f"Erreur SQL : {err} | Query : {query} | Params : {params}"
347
+ )
348
 
349
+ # def fetch_by_condition(self, table_name: str, condition: str, params: Tuple = ()) -> List[Tuple]:
350
+ # """Récupère les données d'une table avec une condition."""
351
+ # query = f"SELECT * FROM {table_name} WHERE {condition}"
352
+ # try:
353
+ # self.cursor.execute(query, params)
354
+ # return self.execute_safe(query, params, fetch=True)
355
+ # except psycopg2.Error as err:
356
+ # logger.error(f"Erreur de connexion : {err}")
357
+ # return
358
 
359
+ def fetch_by_condition(
360
+ self, table_name: str, condition: str, params: Tuple = ()
361
+ ) -> List[Tuple]:
362
+ """
363
+ Récupère les données d'une table avec une condition.
364
+
365
+ Args:
366
+ table_name (str): nom de la table de laquelle récupérer des données.
367
+ condition (str): condition qui sera inclue dans la clause WHERE pour filtrage.
368
+ params (Tuple): paramètres de la requête.
369
+
370
+ Returns:
371
+ List[Tuple]: résultats de la requête (si fetch est True), sinon None (via la fonction execute_safe).
372
+ """
373
  query = f"SELECT * FROM {table_name} WHERE {condition}"
374
  try:
375
+ # Utilise execute_safe pour exécuter la requête et récupérer les résultats
376
  return self.execute_safe(query, params, fetch=True)
377
+ except sqlite3.Error as err:
378
+ logger.error(
379
+ f"Erreur lors de la récupération des données de {table_name} avec condition '{condition}': {err}"
380
+ )
381
+ return []
382
+
383
+ # def update_data(self, table_name: str, set_clause: str, condition: str, params: Tuple) -> None:
384
+ # """Met à jour des données dans une table."""
385
+ # query = f"UPDATE {table_name} SET {set_clause} WHERE {condition}"
386
+ # try:
387
+ # self.cursor.execute(query, params)
388
+ # except psycopg2.Error as err:
389
+ # logger.error(f"Erreur de connexion : {err}")
390
+ # return
391
+ # finally:
392
+ # self.connection.commit()
393
 
394
+ def update_data(
395
+ self, table_name: str, set_clause: str, condition: str, params: Tuple
396
+ ) -> None:
397
+ """
398
+ Met à jour des données dans une table.
399
+
400
+ Args:
401
+ table_name (str): nom de la table dont les données vont être mises à jour.
402
+ set_clause (str): information qui sera inclue dans la clause SET pour la mise à jour.
403
+ condition (str): condition qui sera inclue dans la clause WHERE pour filtrage.
404
+ params (Tuple): paramètres de la requête.
405
+ """
406
  query = f"UPDATE {table_name} SET {set_clause} WHERE {condition}"
407
  try:
408
  self.cursor.execute(query, params)
409
+ except sqlite3.Error as err:
410
+ logger.error(
411
+ f"Erreur lors de la mise à jour des données dans {table_name} : {err}"
412
+ )
413
  finally:
414
+ self.connection.commit() # Valider les modifications
415
+
416
+ # def delete_data(self, table_name: str, condition: str, params: Tuple) -> None:
417
+ # """Supprime des données d'une table selon une condition."""
418
+ # query = f"DELETE FROM {table_name} WHERE {condition}"
419
+ # try:
420
+ # self.cursor.execute(query, params)
421
+ # except psycopg2.Error as err:
422
+ # logger.error(f"Erreur de connexion : {err}")
423
+ # return
424
+ # finally:
425
+ # self.connection.commit()
426
 
427
  def delete_data(self, table_name: str, condition: str, params: Tuple) -> None:
428
+ """
429
+ Supprime des données d'une table selon une condition.
430
+
431
+ Args:
432
+ table_name (str): nom de la table dont les données vont être suprimées.
433
+ condition (str): condition qui sera inclue dans la clause WHERE pour filtrage.
434
+ params (Tuple): paramètres de la requête.
435
+ """
436
  query = f"DELETE FROM {table_name} WHERE {condition}"
437
  try:
438
  self.cursor.execute(query, params)
439
+ except sqlite3.Error as err:
440
+ logger.error(
441
+ f"Erreur lors de la suppression des données dans {table_name} : {err}"
442
+ )
443
  finally:
444
+ self.connection.commit() # Valider les modifications
445
+
446
+ # def close_connection(self) -> None:
447
+ # """Ferme la connexion à la base de données."""
448
+ # if self.connection:
449
+ # self.cursor.close()
450
+ # self.connection.close()
451
 
452
  def close_connection(self) -> None:
453
+ """
454
+ Ferme la connexion à la base de données.
455
+ """
456
  if self.connection:
457
+ try:
458
+ self.cursor.close() # Fermer le curseur
459
+ self.connection.close() # Fermer la connexion
460
+ except sqlite3.Error as err:
461
+ logger.error(f"Erreur lors de la fermeture de la connexion : {err}")
462
+
463
+ # def create_index(self, table_name: str, column_name: str) -> None:
464
+ # """Crée un index sur une colonne spécifique pour améliorer les performances de recherche."""
465
+ # query = f"CREATE INDEX IF NOT EXISTS idx_{table_name}_{column_name} ON {table_name} ({column_name})"
466
+ # try:
467
+ # self.cursor.execute(query)
468
+ # except psycopg2.Error as err:
469
+ # logger.error(f"Erreur de connexion : {err}")
470
+ # return
471
+ # finally:
472
+ # self.connection.commit()
473
 
474
  def create_index(self, table_name: str, column_name: str) -> None:
475
+ """
476
+ Crée un index sur une colonne spécifique pour améliorer les performances de recherche.
477
+ """
478
  query = f"CREATE INDEX IF NOT EXISTS idx_{table_name}_{column_name} ON {table_name} ({column_name})"
479
  try:
480
  self.cursor.execute(query)
481
+ except sqlite3.Error as err:
482
+ logger.error(
483
+ f"Erreur lors de la création de l'index sur {table_name} ({column_name}) : {err}"
484
+ )
485
  finally:
486
+ self.connection.commit() # Valider les modifications
487
+
488
+ # def select(self, query: str, params: Tuple = ()) -> List[Tuple]:
489
+ # """Exécute une requête SELECT personnalisée et retourne les résultats."""
490
+ # try:
491
+ # self.cursor.execute(query, params)
492
+ # return self.cursor.fetchall()
493
+ # except psycopg2.Error as err:
494
+ # logger.error(f"Erreur de connexion : {err}")
495
+ # return
496
 
497
  def select(self, query: str, params: Tuple = ()) -> List[Tuple]:
498
+ """
499
+ Exécute une requête SELECT personnalisée et retourne les résultats.
500
+
501
+ Args:
502
+ query (str): requête SELECT.
503
+ params (Tuple): paramètres de la requête.
504
+
505
+ Returns:
506
+ List[Tuple]: liste des enregistrements récupérés.
507
+ """
508
  try:
509
  self.cursor.execute(query, params)
510
  return self.cursor.fetchall()
511
+ except sqlite3.Error as err:
512
+ logger.error(f"Erreur lors de l'exécution de la requête SELECT : {err}")
513
+ return []
514
+
515
+ # def query(self, query, params=None):
516
+ # """
517
+ # Exécute une requête SQL, en utilisant les paramètres fournis,
518
+ # et retourne les résultats si nécessaire.
519
+ # """
520
+ # try:
521
+ # self.cursor.execute(query, params)
522
+ # except psycopg2.Error as err:
523
+ # logger.error(f"Erreur de connexion : {err}")
524
+ # return
525
+ # finally:
526
+ # # Si la requête est un SELECT, récupérer les résultats
527
+ # if query.strip().upper().startswith("SELECT"):
528
+ # return self.cursor.fetchall()
529
+ # else: # Si ce n'est pas un SELECT, ne rien retourner (utile pour INSERT/UPDATE)
530
+ # self.connection.commit()
531
+ # return None
532
+
533
+ def query(self, query: str, params: Tuple = None) -> List[Tuple]:
534
  """
535
+ Exécute une requête SQL, en utilisant les paramètres fournis, et retourne les résultats si nécessaire.
536
+
537
+ Args:
538
+ query (str): reqête SQL.
539
+ params (Tuple): paramètres de la requête.
540
+
541
+ Returns:
542
+ List[Tuple]: list des enregistrements récupérés s'il s'agit d'une requête SELECT, None sinon.
543
  """
544
  try:
545
+ if params is None:
546
+ self.cursor.execute(query)
547
+ else:
548
+ self.cursor.execute(query, params)
549
+ except sqlite3.Error as err:
550
+ logger.error(f"Erreur lors de l'exécution de la requête : {err}")
551
  return
552
  finally:
553
  # Si la requête est un SELECT, récupérer les résultats
554
  if query.strip().upper().startswith("SELECT"):
555
  return self.cursor.fetchall()
556
+ else: # Si ce n'est pas un SELECT, ne rien retourner (utile pour INSERT/UPDATE)
557
  self.connection.commit()
558
+ return None
 
559
 
560
 
 
 
561
  ######################### FONCTIONS #########################
562
 
563
  # Mettre DBManager en cache
564
  @st.cache_resource
565
  def get_db_manager():
566
+ return DBManager(db_config, os.path.join("server", "db", "schema.json"))
567
+
568
 
569
+ # def save_message(db_manager, id_conversation: int, role: str, content: str,temps_traitement, total_cout, impact_eco) -> None:
570
+ # """
571
+ # Sauvegarde un message dans la base de données, en associant l'utilisateur à la conversation.
572
 
573
+ # :param db_manager: Instance de DBManager.
574
+ # :param id_conversation: ID de la conversation associée.
575
+ # :param role: Rôle de l'intervenant (ex. 'user' ou 'assistant').
576
+ # :param content: Contenu du message.
577
+ # """
578
+ # timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
579
+ # data = [{
580
+ # "id_conversation": id_conversation,
581
+ # "role": role,
582
+ # "content": content,
583
+ # "timestamp": timestamp,
584
+ # "temps_traitement":temps_traitement,
585
+ # "total_cout": total_cout,
586
+ # "impact_eco": impact_eco
587
+ # }]
588
+ # try:
589
+ # db_manager.insert_data_from_dict("messages", data, id_column="id_message")
590
+ # except psycopg2.Error as err:
591
+ # logger.error(f"Erreur de connexion: {err}")
592
+ # return
593
+
594
+
595
+ def save_message(
596
+ db_manager,
597
+ id_conversation: int,
598
+ role: str,
599
+ content: str,
600
+ temps_traitement: float,
601
+ total_cout: float,
602
+ impact_eco: float,
603
+ ) -> None:
604
  """
605
  Sauvegarde un message dans la base de données, en associant l'utilisateur à la conversation.
606
 
607
+ Args:
608
+ db_manager: instance de DBManager.
609
+ id_conversation (int): ID de la conversation associée.
610
+ role (str): rôle de l'intervenant (ex. 'user' ou 'assistant').
611
+ content (str): contenu du message.
612
+ temps_traitement (float): temps de traitement.
613
+ total_cout (float): coût total associé au message.
614
+ impact_eco (float): impact économique du message.
615
  """
616
  timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
617
+ data = [
618
+ {
619
+ "id_conversation": id_conversation,
620
+ "role": role,
621
+ "content": content,
622
+ "timestamp": timestamp,
623
+ "temps_traitement": temps_traitement,
624
+ "total_cout": total_cout,
625
+ "impact_eco": impact_eco,
626
+ }
627
+ ]
628
+
629
  try:
630
+ # Insertion des données dans la table "messages" en utilisant la méthode d'insertion adaptée
631
+ db_manager.insert_data_from_dict("messages", data)
632
+ except sqlite3.Error as err: # Utilisation de sqlite3.Error pour gérer les exceptions SQLite
633
+ logger.error(f"Erreur lors de la sauvegarde du message : {err}")
634
+ return
635
+
636
+
637
+ # def create_conversation(db_manager, title: str, id_utilisateur: int) -> int:
638
+ # """
639
+ # Crée une nouvelle conversation dans la base de données, en associant l'utilisateur à la conversation.
640
+
641
+ # :param db_manager: Instance de DBManager.
642
+ # :param title: Titre de la conversation.
643
+ # :param id_utilisateur: ID de l'utilisateur associé.
644
+ # :return: ID de la conversation nouvellement créée.
645
+ # """
646
+ # created_at = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
647
+ # data = [{
648
+ # "created_at": created_at,
649
+ # "title": title,
650
+ # "id_utilisateur": id_utilisateur,
651
+ # }]
652
+ # try:
653
+ # result = db_manager.insert_data_from_dict("conversations", data, id_column="id_conversation")
654
+ # return result[0]
655
+ # except psycopg2.Error as err:
656
+ # logger.error(f"Erreur de connexion : {err}")
657
+ # return
658
+
659
 
660
  def create_conversation(db_manager, title: str, id_utilisateur: int) -> int:
661
  """
662
  Crée une nouvelle conversation dans la base de données, en associant l'utilisateur à la conversation.
663
 
664
+ Args:
665
+ db_manager: instance de DBManager.
666
+ title (str): titre de la conversation.
667
+ id_utilisateur (int): ID de l'utilisateur associé.
668
+
669
+ Returns:
670
+ int: ID de la conversation nouvellement créée.
671
+
672
  """
673
  created_at = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
674
+ data = [
675
+ {"created_at": created_at, "title": title, "id_utilisateur": id_utilisateur,}
676
+ ]
677
+
 
678
  try:
679
+ result = db_manager.insert_data_from_dict("conversations", data)
680
+ return result[0] # Retourne l'ID de la conversation nouvellement créée
681
+ except sqlite3.Error as err: # Gestion des erreurs avec sqlite3
682
+ logger.error(f"Erreur lors de la création de la conversation : {err}")
683
+ return None
684
+
685
+
686
+ # def load_messages(db_manager, id_conversation: int) -> List[Dict]:
687
+ # """
688
+ # Charge les messages associés à une conversation.
689
+
690
+ # :param db_manager: Instance de DBManager.
691
+ # :param id_conversation: ID de la conversation.
692
+ # :return: Liste des messages sous forme de dictionnaires.
693
+ # """
694
+ # query = """
695
+ # SELECT *
696
+ # FROM messages
697
+ # WHERE id_conversation = %s
698
+ # ORDER BY timestamp ASC
699
+ # """
700
+ # try:
701
+ # result = db_manager.query(query, (id_conversation,))
702
+ # return [{"role": row["role"], "content": row["content"], "timestamp":row["timestamp"], "temps_traitement":row["temps_traitement"]} for row in result]
703
+ # except psycopg2.Error as err:
704
+ # logger.error(f"Erreur de connexion : {err}")
705
+ # return
706
+
707
 
708
  def load_messages(db_manager, id_conversation: int) -> List[Dict]:
709
  """
710
  Charge les messages associés à une conversation.
711
 
712
+ Args:
713
+ db_manager: instance de DBManager.
714
+ id_conversation (int): ID de la conversation.
715
+ Returns:
716
+ List[Dict]: liste des messages sous forme de dictionnaires.
717
  """
718
  query = """
719
+ SELECT role, content, timestamp, temps_traitement
720
  FROM messages
721
+ WHERE id_conversation = ?
722
  ORDER BY timestamp ASC
723
  """
724
  try:
725
  result = db_manager.query(query, (id_conversation,))
726
+ # Résultats sous forme de dictionnaires, si la fonction query retourne des tuples ou dictionnaires
727
+ return [
728
+ {
729
+ "role": row["role"],
730
+ "content": row["content"],
731
+ "timestamp": row["timestamp"],
732
+ "temps_traitement": row["temps_traitement"],
733
+ }
734
+ for row in result
735
+ ]
736
+ except sqlite3.Error as err: # Utilisation de sqlite3.Error pour capturer les erreurs SQLite
737
+ logger.error(f"Erreur lors du chargement des messages : {err}")
738
+ return []
739
+
740
+
741
+ # def load_conversations(db_manager, id_utilisateur: int) -> List[Dict]:
742
+ # """
743
+ # Charge toutes les conversations enregistrées pour un utilisateur donné.
744
+
745
+ # :param db_manager: Instance de DBManager.
746
+ # :param id_utilisateur: ID de l'utilisateur.
747
+ # :return: Liste des conversations.
748
+ # """
749
+ # query = """
750
+ # SELECT * FROM conversations
751
+ # WHERE id_utilisateur = %s
752
+ # ORDER BY created_at DESC
753
+ # """
754
+ # try:
755
+ # result = db_manager.query(query, (id_utilisateur,))
756
+
757
+
758
+ # return [
759
+ # {"id_conversation": row["id_conversation"], "created_at": row["created_at"], "title": row["title"]} for row in result
760
+ # ]
761
+ # except psycopg2.Error as err:
762
+ # logger.error(f"Erreur de connexion : {err}")
763
+ # return
764
+
765
 
766
  def load_conversations(db_manager, id_utilisateur: int) -> List[Dict]:
767
  """
768
  Charge toutes les conversations enregistrées pour un utilisateur donné.
769
 
770
+ Args:
771
+ db_manager: instance de DBManager.
772
+ id_utilisateur (int): ID de l'utilisateur.
773
+ Returns:
774
+ List[Dict]: liste des conversations.
775
  """
776
  query = """
777
+ SELECT id_conversation, created_at, title
778
+ FROM conversations
779
+ WHERE id_utilisateur = ?
780
  ORDER BY created_at DESC
781
  """
782
  try:
783
  result = db_manager.query(query, (id_utilisateur,))
 
 
784
  return [
785
+ {
786
+ "id_conversation": row["id_conversation"],
787
+ "created_at": row["created_at"],
788
+ "title": row["title"],
789
+ }
790
+ for row in result
791
  ]
792
+ except sqlite3.Error as err: # Gestion des erreurs avec sqlite3
793
+ logger.error(f"Erreur lors du chargement des conversations : {err}")
794
+ return []
795
+
796
+
797
+ # def update_conversation(db_manager, id_conversation: int, id_utilisateur: int) -> None:
798
+ # """
799
+ # Met à jour le champ `created_at` d'une conversation donnée pour un utilisateur.
800
+
801
+ # :param db_manager: Instance de DBManager.
802
+ # :param id_conversation: ID de la conversation à mettre à jour.
803
+ # :param id_utilisateur: ID de l'utilisateur.
804
+ # """
805
+ # new_timer = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
806
+ # query = """
807
+ # UPDATE conversations
808
+ # SET created_at = %s
809
+ # WHERE id_conversation = %s AND id_utilisateur = %s
810
+ # """
811
+ # try:
812
+ # db_manager.query(query, (new_timer, id_conversation, id_utilisateur))
813
+ # except psycopg2.Error as err:
814
+ # logger.error(f"Erreur de connexion : {err}")
815
+ # return
816
+
817
 
818
  def update_conversation(db_manager, id_conversation: int, id_utilisateur: int) -> None:
819
  """
820
  Met à jour le champ `created_at` d'une conversation donnée pour un utilisateur.
821
 
822
+ Args:
823
+ db_manager: instance de DBManager.
824
+ id_conversation (int): ID de la conversation à mettre à jour.
825
+ id_utilisateur (int): ID de l'utilisateur.
826
  """
827
  new_timer = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
828
  query = """
829
  UPDATE conversations
830
+ SET created_at = ?
831
+ WHERE id_conversation = ? AND id_utilisateur = ?
832
  """
833
  try:
834
  db_manager.query(query, (new_timer, id_conversation, id_utilisateur))
835
+ except sqlite3.Error as err: # Utilisation de sqlite3.Error pour capturer les erreurs SQLite
836
+ logger.error(f"Erreur lors de la mise à jour de la conversation : {err}")
837
  return
838
 
839
+
840
+ # def update_conversation_title(db_manager, id_conversation: int, new_title: str) -> None:
841
+ # """
842
+ # Met à jour le titre d'une conversation si celui-ci est encore "Nouvelle conversation".
843
+
844
+ # :param db_manager: Instance de DBManager.
845
+ # :param id_conversation: ID de la conversation à mettre à jour.
846
+ # :param new_title: Nouveau titre de la conversation.
847
+ # """
848
+ # query = """
849
+ # UPDATE conversations
850
+ # SET title = %s
851
+ # WHERE id_conversation = %s AND title = 'Nouvelle conversation'
852
+ # """
853
+ # try:
854
+ # db_manager.query(query, (new_title, id_conversation))
855
+ # except psycopg2.Error as err:
856
+ # logger.error(f"Erreur de connexion : {err}")
857
+ # return
858
+
859
+
860
  def update_conversation_title(db_manager, id_conversation: int, new_title: str) -> None:
861
  """
862
  Met à jour le titre d'une conversation si celui-ci est encore "Nouvelle conversation".
863
 
864
+ Args:
865
+ db_manager: instance de DBManager.
866
+ id_conversation (int): ID de la conversation à mettre à jour.
867
+ new_title (str): nouveau titre de la conversation.
868
  """
869
  query = """
870
  UPDATE conversations
871
+ SET title = ?
872
+ WHERE id_conversation = ? AND title = 'Nouvelle conversation'
873
  """
874
  try:
875
  db_manager.query(query, (new_title, id_conversation))
876
+ except sqlite3.Error as err: # Utilisation de sqlite3.Error pour gérer les erreurs SQLite
877
+ logger.error(
878
+ f"Erreur lors de la mise à jour du titre de la conversation : {err}"
879
+ )
880
  return
881
 
882
+
883
+ # def get_conversation_title(db_manager, id_conversation: int) -> str:
884
+ # """
885
+ # Récupère le titre d'une conversation spécifique en utilisant `fetch_by_condition`.
886
+
887
+ # :param db_manager: Instance de DBManager.
888
+ # :param id_conversation: ID de la conversation à interroger.
889
+ # :return: Le titre de la conversation ou "Nouvelle conversation".
890
+ # """
891
+ # table_name = "conversations"
892
+ # condition = "id_conversation = %s"
893
+ # try:
894
+ # results = db_manager.fetch_by_condition(table_name, condition, (id_conversation,))
895
+ # if results:
896
+ # # Suppose que `title` est la troisième colonne
897
+ # return results[0][2]
898
+ # return "Nouvelle conversation"
899
+ # except psycopg2.Error as err:
900
+ # logger.error(f"Erreur de connexion : {err}")
901
+ # return
902
+
903
+
904
  def get_conversation_title(db_manager, id_conversation: int) -> str:
905
  """
906
  Récupère le titre d'une conversation spécifique en utilisant `fetch_by_condition`.
907
 
908
+ Args:
909
+ db_manager: instance de DBManager.
910
+ id_conversation (int): ID de la conversation à interroger.
911
+
912
+ Returns:
913
+ str: le titre de la conversation ou "Nouvelle conversation".
914
  """
915
  table_name = "conversations"
916
+ condition = "id_conversation = ?"
917
  try:
918
+ results = db_manager.fetch_by_condition(
919
+ table_name, condition, (id_conversation,)
920
+ )
921
  if results:
922
+ # Assume that `title` is the second column
923
+ return results[0][
924
+ 1
925
+ ] # 1 corresponds to the index of the title column in the result
926
  return "Nouvelle conversation"
927
+ except sqlite3.Error as err: # Utilisation de sqlite3.Error pour gérer les erreurs SQLite
928
+ logger.error(
929
+ f"Erreur lors de la récupération du titre de la conversation : {err}"
930
+ )
931
+ return "Nouvelle conversation"
932
+
933
+
934
+ # def delete_conversation(db_manager, id_conversation: int) -> None:
935
+ # """
936
+ # Supprime une conversation et ses messages associés de la base de données.
937
+
938
+ # :param db_manager: Instance de DBManager.
939
+ # :param id_conversation: ID de la conversation à supprimer.
940
+ # """
941
+ # try:
942
+ # # Supprimer les messages liés à la conversation
943
+ # query_delete_messages = "DELETE FROM messages WHERE id_conversation = %s"
944
+ # db_manager.query(query_delete_messages, (id_conversation,))
945
+
946
+ # # Supprimer la conversation elle-même
947
+ # query_delete_conversation = "DELETE FROM conversations WHERE id_conversation = %s"
948
+ # db_manager.query(query_delete_conversation, (id_conversation,))
949
+
950
+ # print(f"✅ Conversation {id_conversation} supprimée avec succès.")
951
+ # except Exception as e:
952
+ # print(f"❌ Erreur lors de la suppression de la conversation {id_conversation}: {e}")
953
+
954
 
955
  def delete_conversation(db_manager, id_conversation: int) -> None:
956
  """
957
  Supprime une conversation et ses messages associés de la base de données.
958
 
959
+ Args:
960
+ db_manager: instance de DBManager.
961
+ id_conversation (int): ID de la conversation à supprimer.
962
  """
963
  try:
964
  # Supprimer les messages liés à la conversation
965
+ query_delete_messages = "DELETE FROM messages WHERE id_conversation = ?"
966
  db_manager.query(query_delete_messages, (id_conversation,))
967
 
968
  # Supprimer la conversation elle-même
969
+ query_delete_conversation = (
970
+ "DELETE FROM conversations WHERE id_conversation = ?"
971
+ )
972
  db_manager.query(query_delete_conversation, (id_conversation,))
973
 
974
+ logger.info(f"✅ Conversation {id_conversation} supprimée avec succès.")
975
+ # print(f"✅ Conversation {id_conversation} supprimée avec succès.")
976
+ except sqlite3.Error as e: # Utilisation de sqlite3.Error pour capturer les erreurs SQLite
977
+ logger.error(
978
+ f"❌ Erreur lors de la suppression de la conversation {id_conversation}: {e}"
979
+ )
980
+ # print(f"❌ Erreur lors de la suppression de la conversation {id_conversation}: {e}")
981
+
982
+
983
+ # def load_chatbot_suggestions(db_manager, user_id):
984
+ # """
985
+ # Charge les suggestions du chatbot enregistrées pour un utilisateur donné.
986
+ # """
987
+ # query = "SELECT repas_suggestion FROM suggestions_repas WHERE id_utilisateur = %s"
988
+ # try:
989
+ # db_manager.cursor.execute(query, (user_id,))
990
+ # suggestions = [row[0] for row in db_manager.cursor.fetchall()]
991
+ # return suggestions
992
+ # except psycopg2.Error as err:
993
+ # logger.error(f"Erreur lors du chargement des suggestions : {err}")
994
+ # return []
995
 
996
+
997
+ def load_chatbot_suggestions(db_manager, user_id: str) -> List[Tuple]:
998
  """
999
  Charge les suggestions du chatbot enregistrées pour un utilisateur donné.
1000
+
1001
+ Args:
1002
+ db_manager: instance de DBManager.
1003
+ user_id (int): ID de l'utilisateur.
1004
+
1005
+ Returns:
1006
+ List[Tuple]: list des suggestions du chatbot.
1007
  """
1008
+ query = "SELECT repas_suggestion FROM suggestions_repas WHERE id_utilisateur = ?"
1009
  try:
1010
  db_manager.cursor.execute(query, (user_id,))
1011
  suggestions = [row[0] for row in db_manager.cursor.fetchall()]
1012
  return suggestions
1013
+ except sqlite3.Error as err: # Utilisation de sqlite3.Error pour capturer les erreurs SQLite
1014
  logger.error(f"Erreur lors du chargement des suggestions : {err}")
1015
  return []
1016
 
1017
 
1018
+ # def save_chatbot_suggestions(db_manager, user_id, suggestions):
1019
+ # """
1020
+ # Sauvegarde les suggestions du chatbot dans la base de données.
1021
+ # """
1022
+ # query = """
1023
+ # INSERT INTO suggestions_repas (id_utilisateur, repas_suggestion, motif_suggestion)
1024
+ # VALUES (%s, %s, %s)
1025
+ # """
1026
+ # try:
1027
+ # for suggestion in suggestions:
1028
+ # db_manager.cursor.execute(query, (user_id, suggestion, "Chatbot"))
1029
+ # db_manager.connection.commit()
1030
+ # except psycopg2.Error as err:
1031
+ # logger.error(f"Erreur lors de l'enregistrement des suggestions : {err}")
1032
 
1033
+
1034
+ def save_chatbot_suggestions(db_manager, user_id, suggestions: List[Tuple]):
1035
  """
1036
  Sauvegarde les suggestions du chatbot dans la base de données.
1037
+
1038
+ Args:
1039
+ db_manager: instance de DBManager.
1040
+ user_id (int): ID de l'utilisateur.
1041
+ suggestions (List[Tuple]): list des suggestions du chatbot.
1042
+
1043
  """
1044
  query = """
1045
  INSERT INTO suggestions_repas (id_utilisateur, repas_suggestion, motif_suggestion)
1046
+ VALUES (?, ?, ?)
1047
  """
1048
  try:
1049
  for suggestion in suggestions:
1050
  db_manager.cursor.execute(query, (user_id, suggestion, "Chatbot"))
1051
  db_manager.connection.commit()
1052
+ except sqlite3.Error as err: # Remplacer psycopg2.Error par sqlite3.Error pour SQLite
1053
  logger.error(f"Erreur lors de l'enregistrement des suggestions : {err}")
 
 
server/db/schema.json CHANGED
@@ -1,85 +1,3 @@
1
- {
2
- "tables": {
3
- "utilisateurs": {
4
- "columns": [
5
- {"name": "id_utilisateur", "type": "SERIAL", "constraints": ["PRIMARY KEY"]},
6
- {"name": "login", "type": "TEXT", "constraints": ["UNIQUE", "NOT NULL"]},
7
- {"name": "mot_de_passe", "type": "TEXT", "constraints": ["NOT NULL"]},
8
- {"name": "nom", "type": "TEXT", "constraints": []},
9
- {"name": "objectifs_nutritionnels", "type": "TEXT", "constraints": []},
10
- {"name": "poids", "type": "INTEGER", "constraints": []},
11
- {"name": "Taille", "type": "INTEGER", "constraints": []},
12
- {"name": "activite_physique", "type": "TEXT", "constraints": []},
13
- {"name": "objectif_calorique", "type": "TEXT", "constraints": []},
14
- {"name": "regime_particulier", "type": "TEXT", "constraints": []},
15
- {"name": "email", "type": "TEXT", "constraints": []},
16
- {"name": "date_creation", "type": "TIMESTAMP", "constraints": ["DEFAULT CURRENT_TIMESTAMP"]},
17
- {"name": "date_derniere_connexion", "type": "TIMESTAMP", "constraints": []}
18
- ]
19
- },
20
- "recettes": {
21
- "columns": [
22
- {"name": "id_recette", "type": "SERIAL", "constraints": ["PRIMARY KEY"]},
23
- {"name": "titre", "type": "TEXT", "constraints": ["NOT NULL"]},
24
- {"name": "infos", "type": "TEXT", "constraints": []},
25
- {"name": "ingredients", "type": "TEXT", "constraints": ["NOT NULL"]},
26
- {"name": "instructions", "type": "TEXT", "constraints": ["NOT NULL"]},
27
- {"name": "temps_preparation", "type": "TEXT", "constraints": []},
28
- {"name": "infos_regime", "type": "TEXT", "constraints": []},
29
- {"name": "valeurs_100g", "type": "TEXT", "constraints": []},
30
- {"name": "valeurs_portion", "type": "TEXT", "constraints": []},
31
- {"name": "all_infos", "type": "TEXT", "constraints": []},
32
- {"name": "cleaned_infos", "type": "TEXT", "constraints": []},
33
- {"name": "cluster2", "type": "INTEGER", "constraints": []}
34
- ]
35
- },
36
- "historique_repas": {
37
- "columns": [
38
- {"name": "id_historique", "type": "SERIAL", "constraints": ["PRIMARY KEY"]},
39
- {"name": "id_utilisateur", "type": "INTEGER", "constraints": ["NOT NULL", "REFERENCES utilisateurs(id_utilisateur)"]},
40
- {"name": "id_recette", "type": "INTEGER", "constraints": ["NOT NULL", "REFERENCES recettes(id_recette)"]},
41
- {"name": "date_heure", "type": "TIMESTAMP", "constraints": ["DEFAULT CURRENT_TIMESTAMP"]},
42
- {"name": "quantite", "type": "INTEGER", "constraints": []}
43
- ]
44
- },
45
- "conversations": {
46
- "columns": [
47
- {"name": "id_conversation", "type": "SERIAL", "constraints": ["PRIMARY KEY"]},
48
- {"name": "id_utilisateur", "type": "INTEGER", "constraints": ["NOT NULL", "REFERENCES utilisateurs(id_utilisateur)"]},
49
- {"name": "title", "type": "TEXT", "constraints": ["NOT NULL"]},
50
- {"name": "created_at", "type": "TIMESTAMP", "constraints": ["NOT NULL"]}
51
- ]
52
- },
53
- "messages": {
54
- "columns": [
55
- {"name": "id_message", "type": "SERIAL", "constraints": ["PRIMARY KEY"]},
56
- {"name": "id_conversation", "type": "INTEGER", "constraints": ["NOT NULL", "REFERENCES conversations(id_conversation)"]},
57
- {"name": "role", "type": "TEXT", "constraints": ["NOT NULL"]},
58
- {"name": "content", "type": "TEXT", "constraints": ["NOT NULL"]},
59
- {"name": "timestamp", "type": "TIMESTAMP", "constraints": ["DEFAULT CURRENT_TIMESTAMP"]},
60
- {"name": "temps_traitement", "type": "REAL", "constraints": []},
61
- {"name": "contexte", "type": "TEXT", "constraints": []},
62
- {"name": "total_cout", "type": "REAL", "constraints": []},
63
- {"name": "impact_eco", "type": "REAL", "constraints": []}
64
- ]
65
- },
66
- "liste_courses": {
67
- "columns": [
68
- {"name": "id_liste", "type": "SERIAL", "constraints": ["PRIMARY KEY"]},
69
- {"name": "id_utilisateur", "type": "INTEGER", "constraints": ["NOT NULL", "REFERENCES utilisateurs(id_utilisateur)"]},
70
- {"name": "ingredients", "type": "TEXT", "constraints": ["NOT NULL"]},
71
- {"name": "date_creation", "type": "TIMESTAMP", "constraints": ["DEFAULT CURRENT_TIMESTAMP"]},
72
- {"name": "status", "type": "TEXT", "constraints": []}
73
- ]
74
- },
75
- "suggestions_repas": {
76
- "columns": [
77
- {"name": "id_suggestion", "type": "SERIAL", "constraints": ["PRIMARY KEY"]},
78
- {"name": "id_utilisateur", "type": "INTEGER", "constraints": ["NOT NULL", "REFERENCES utilisateurs(id_utilisateur)"]},
79
- {"name": "repas_suggestion", "type": "TEXT", "constraints": ["NOT NULL"]},
80
- {"name": "date_heure", "type": "TIMESTAMP", "constraints": ["DEFAULT CURRENT_TIMESTAMP"]},
81
- {"name": "motif_suggestion", "type": "TEXT", "constraints": []}
82
- ]
83
- }
84
- }
85
- }
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:92b0932c8f60048b055922f2022bccf01e3558d75e547e3a0b0a22634328ae4d
3
+ size 4830
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/db/schema_postgreSQL.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:97dd9dd3388d2749d3690c7f6fa7bc2ae4e3d91eca472f89b7f8016620c99c02
3
+ size 5018