Cherryblade29 commited on
Commit
8e0449f
·
verified ·
1 Parent(s): 1e187f1

Upload normalisation_script.py

Browse files
Files changed (1) hide show
  1. normalisation_script.py +479 -0
normalisation_script.py ADDED
@@ -0,0 +1,479 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sqlite3
3
+ import json
4
+ import pandas as pd
5
+ import argparse
6
+ import re
7
+ from agno.agent import Agent
8
+ from agno.models.groq import Groq
9
+ from dotenv import load_dotenv
10
+ import sys
11
+ from clean_database import clean_database
12
+
13
+
14
+
15
+
16
+
17
+ sys.stdout.reconfigure(encoding='utf-8')
18
+
19
+ load_dotenv()
20
+
21
+ def detecter_format(fichier):
22
+ """Retourne l'extension du fichier sous forme explicite."""
23
+ extension = os.path.splitext(fichier)[1].lower()
24
+
25
+ if extension in [".db", ".sqlite"]:
26
+ return "sqlite"
27
+ elif extension == ".json":
28
+ return "json"
29
+ elif extension == ".csv":
30
+ return "csv"
31
+ elif extension in [".xls", ".xlsx"]:
32
+ return "excel"
33
+ else:
34
+ return "inconnu"
35
+
36
+ def json_to_sqlite(fichier_json):
37
+ """Convertit un fichier JSON en base SQLite."""
38
+ try:
39
+ with open(fichier_json, 'r') as file:
40
+ data = json.load(file)
41
+ except json.JSONDecodeError as e:
42
+ print(f"Erreur lors de la lecture du fichier JSON : {e}")
43
+ return None
44
+
45
+ db_name = 'temp_json.db'
46
+ conn = sqlite3.connect(db_name)
47
+ cursor = conn.cursor()
48
+
49
+ # Supposons que les données JSON sont une liste de dictionnaires (une table)
50
+ table_name = "data"
51
+ columns = ', '.join(data[0].keys())
52
+ placeholders = ', '.join('?' * len(data[0]))
53
+
54
+ cursor.execute(f"CREATE TABLE IF NOT EXISTS {table_name} ({columns})")
55
+
56
+ for row in data:
57
+ cursor.execute(f"INSERT INTO {table_name} ({columns}) VALUES ({placeholders})", tuple(row.values()))
58
+
59
+ conn.commit()
60
+ conn.close()
61
+
62
+ print(f"✅ Fichier JSON converti avec succès en {db_name}")
63
+ return db_name
64
+
65
+ def csv_to_sqlite(fichier_csv):
66
+ """Convertit un fichier CSV en base SQLite."""
67
+ try:
68
+ df = pd.read_csv(fichier_csv)
69
+ except pd.errors.ParserError as e:
70
+ print(f"Erreur lors de la lecture du fichier CSV : {e}")
71
+ return None
72
+
73
+ db_name = 'temp_csv.db'
74
+ conn = sqlite3.connect(db_name)
75
+ df.to_sql('data', conn, if_exists='replace', index=False)
76
+ conn.close()
77
+
78
+ print(f"✅ Fichier CSV converti avec succès en {db_name}")
79
+ return db_name
80
+
81
+ def excel_to_sqlite(fichier_excel):
82
+ """Convertit un fichier Excel en base SQLite."""
83
+ try:
84
+ df = pd.read_excel(fichier_excel)
85
+ except Exception as e:
86
+ print(f"Erreur lors de la lecture du fichier Excel : {e}")
87
+ return None
88
+
89
+ db_name = 'temp_excel.db'
90
+ conn = sqlite3.connect(db_name)
91
+ df.to_sql('data', conn, if_exists='replace', index=False)
92
+ conn.close()
93
+
94
+ print(f"✅ Fichier Excel converti avec succès en {db_name}")
95
+ return db_name
96
+
97
+ def preparer_bdd(input_path):
98
+ """Détecte le type du fichier et le convertit en SQLite si nécessaire."""
99
+ format_detecte = detecter_format(input_path)
100
+ print(f"📂 Format détecté : {format_detecte.upper()}")
101
+
102
+ if format_detecte == "sqlite":
103
+ print("➡️ Aucun traitement requis, base SQLite déjà prête.")
104
+ return input_path
105
+ elif format_detecte == "json":
106
+ return json_to_sqlite(input_path)
107
+ elif format_detecte == "csv":
108
+ return csv_to_sqlite(input_path)
109
+ elif format_detecte == "excel":
110
+ return excel_to_sqlite(input_path)
111
+ else:
112
+ print("❌ Format non supporté. Utilise un fichier .db, .json, .csv, .xls ou .xlsx")
113
+ return None
114
+
115
+
116
+
117
+ def extraire_bdd(db_path):
118
+ """Récupère la structure et les données d'une base SQLite."""
119
+ try:
120
+ with sqlite3.connect(db_path) as conn:
121
+ cursor = conn.cursor()
122
+
123
+ # Récupérer les tables
124
+ tables = [table[0] for table in cursor.execute(
125
+ "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%';"
126
+ ).fetchall()]
127
+
128
+ if not tables:
129
+ print("⚠️ Aucune table trouvée.")
130
+ return ""
131
+
132
+ output_bdd = ""
133
+
134
+ for table in tables:
135
+ output_bdd += f"\n📌 Table `{table}` :\n"
136
+
137
+ # Récupérer les colonnes
138
+ cursor.execute(f"PRAGMA table_info({table});")
139
+ columns = cursor.fetchall()
140
+ col_names = [col[1] for col in columns]
141
+ col_types = [col[2] for col in columns]
142
+
143
+ output_bdd += f"📝 Colonnes: {', '.join(f'{name} ({ctype})' for name, ctype in zip(col_names, col_types))}\n"
144
+
145
+ # Récupérer les données de la table
146
+ cursor.execute(f"SELECT * FROM {table}")
147
+ rows = cursor.fetchall()
148
+ if rows:
149
+ output_bdd += "\n".join(f" -> {row}" for row in rows) + "\n"
150
+ else:
151
+ output_bdd += "⚠️ Aucune donnée trouvée.\n"
152
+
153
+ return output_bdd
154
+
155
+ except sqlite3.Error as e:
156
+ print(f"��� Erreur SQLite : {e}")
157
+ return ""
158
+
159
+
160
+
161
+ # Création de l'agent Groq pour la vérification de la normalisation
162
+ normalization_checker = Agent(
163
+ model=Groq(id="deepseek-r1-distill-llama-70b", temperature=0.0, top_p=0),
164
+ description="Vérifie si une base de données est normalisée.",
165
+ instructions=[
166
+ "Analyse la structure de la base de données fournie en entrée.",
167
+ "Détermine si elle respecte les formes normales (1NF, 2NF, 3NF, BCNF).",
168
+ "Migre les données de l'ancienne base de données vers la nouvelle base de données normalisée sans générer de code SQL.",
169
+ "Assurez-vous que toutes les données sont correctement transférées et que les relations sont maintenues.",
170
+ "Si la base n'est pas normalisée, propose une version améliorée sans générer de schéma SQL."
171
+ ],
172
+ markdown=True
173
+ )
174
+
175
+ def analyser_bdd(output_bdd: str):
176
+ """Utilise le premier agent pour analyser et normaliser la base de données."""
177
+ prompt = f"""
178
+ Voici la structure et les données de la base SQLite :
179
+
180
+ {output_bdd}
181
+
182
+ Tu es un expert en base de données exécute l'algorithme suivant pour normaliser et migrer la base de données. Affiche **uniquement** le résultat final sans explication détaillée et Ne pas ajouter de nouvelles colonnes non mentionnées dans la base de données d'origine .
183
+
184
+ ---
185
+
186
+ ### **Algorithme de normalisation et migration**
187
+ Début
188
+ Initialiser une variable `resultat_final` pour accumuler les résultats.
189
+
190
+ # Analyser la structure actuelle de la base de données
191
+ Pour chaque table `T` dans la base de données faire
192
+ Détecter la clé primaire existante ou en attribuer une si nécessaire.
193
+
194
+ # Appliquer la 1NF : Atomisation des attributs
195
+ Si `T` contient des attributs non atomiques alors
196
+ Transformer les attributs en valeurs atomiques.
197
+ Assigner les clés primaires et ajouter les clés étrangères si nécessaire.
198
+ Fin Si
199
+
200
+ # Appliquer la 2NF : Éliminer les dépendances fonctionnelles partielles
201
+ Si `T` contient une clé primaire composite et des attributs qui dépendent d’une partie seulement de la clé alors
202
+ Décomposer `T` en nouvelles tables en respectant la 2NF.
203
+ Assigner les clés primaires et ajouter les clés étrangères si nécessaire.
204
+ Fin Si
205
+
206
+ # Appliquer la 3NF : Éliminer les dépendances transitives
207
+ Si `T` contient des dépendances transitives (un attribut dépend d’un autre attribut non clé) alors
208
+ Décomposer `T` en nouvelles tables pour isoler ces dépendances.
209
+ Assigner les clés primaires et ajouter les clés étrangères si nécessaire.
210
+ Fin Si
211
+
212
+ # Appliquer la BCNF : Suppression des dépendances anormales
213
+ Si `T` contient une dépendance fonctionnelle où un attribut non clé détermine une clé candidate alors
214
+ Décomposer `T` en nouvelles relations respectant la BCNF.
215
+ Assigner les clés primaires et ajouter les clés étrangères si nécessaire.
216
+ Fin Si
217
+
218
+ # Migrer les données
219
+ Pour chaque nouvelle table normalisée faire
220
+ Identifier les données à migrer depuis l'ancienne base de données.
221
+ Transformer ou réorganiser les données selon le schéma normalisé proposé.
222
+ Insérer les données transformées dans la nouvelle table normalisée.
223
+
224
+ Fin Pour
225
+
226
+ # Ajouter les nouvelles tables normalisées au résultat final
227
+ Ajouter "📝 Nouvelles tables proposées :" à `resultat_final`.
228
+ Pour chaque nouvelle table `N` créée faire
229
+ Ajouter "📌 Table `nom_nouvelle_table` :" à `resultat_final`.
230
+ Ajouter "📝 Colonnes: colonne1 (type1), colonne2 (type2), ..." à `resultat_final`.
231
+ Ajouter "🔑 Clé primaire: `colonne_PK`" si définie.
232
+ Ajouter "🔗 Clé étrangère: `colonne_FK` → `table_referencée` (`colonne_referencée`)" si applicable.
233
+ Ajouter "📋 Données :" à `resultat_final`.
234
+ Pour chaque enregistrement migré dans la table faire
235
+ Ajouter " - `valeur1`, `valeur2`, ..." à `resultat_final`.
236
+ Fin Pour
237
+
238
+ Fin Pour
239
+
240
+ # Vérification finale avant affichage
241
+
242
+ Afficher `resultat_final`
243
+ Afficher "✅ Normalisation complète et migration réussie."
244
+
245
+ Fin
246
+
247
+
248
+ ---
249
+ """
250
+
251
+ response = normalization_checker.run(prompt)
252
+ resultat = response.content.strip()
253
+
254
+ # Supprimer le texte entre <think>...</think>
255
+ resultat_sans_think = re.sub(r"<think>.*?</think>", "", resultat, flags=re.DOTALL).strip()
256
+ return resultat_sans_think
257
+
258
+
259
+
260
+ # Création de l'agent 2 (vérifie la proposition de normalisation)
261
+ normalization_validator = Agent(
262
+ model=Groq(id="qwen-2.5-32b", temperature=0.0, top_p=0),
263
+ description="Vérifie si la base de données normalisée proposée est correcte.",
264
+ instructions=[
265
+ "Analyse la normalisation proposée et vérifie si elle respecte les formes normales.",
266
+ "Compare la proposition avec la base de données générée pour s'assurer de leur correspondance.",
267
+ "Donne une proposition améliorée si nécessaire."
268
+ ],
269
+ markdown=True
270
+ )
271
+
272
+ def verifier_normalisation(proposition_normalisee: str, output_bdd: str):
273
+ """Utilise le deuxième agent pour valider la normalisation proposée et sa correspondance avec la base de données générée."""
274
+ prompt = f"""
275
+ Voici la base de données générée après application de la normalisation :
276
+ Vérifie si la normalisation proposée correspond bien à {output_bdd}.
277
+
278
+ Voici une proposition de base de données normalisée :
279
+
280
+ {proposition_normalisee}
281
+
282
+ - Tu es un expert en base de données exécute l'algorithme suivant pour vérifier si la base obtenue correspond bien à la normalisation attendue. Affiche **uniquement** le résultat final sans explication détaillée.
283
+ ---
284
+
285
+ ### **Algorithme de vérification et correction des formes normales**
286
+ Début
287
+ Initialiser une variable `corrections_appliquees` = False
288
+
289
+ Pour chaque table dans la base de données faire
290
+ Si tous les attributs sont atomiques alors
291
+ printf("📌 Vérification de la table `nom_table`")
292
+ printf("✅ La table `nom_table` est en 1NF")
293
+
294
+ Si la table ne contient pas de dépendances fonctionnelles partielles alors
295
+ printf("✅ La table `nom_table` est en 2NF")
296
+
297
+ Si la table ne contient pas de dépendances transitives alors
298
+ printf("✅ La table `nom_table` est en 3NF")
299
+
300
+ Si chaque dépendance fonctionnelle est basée sur une clé candidate alors
301
+ printf("✅ La table `nom_table` est en BCNF")
302
+ Sinon
303
+ printf("❌ La table `nom_table` ne respecte pas la BCNF → Correction appliquée.")
304
+ Appliquer la correction en décomposant la table en relations BCNF.
305
+ corrections_appliquees = True
306
+ Fin Si
307
+ Sinon
308
+ printf("❌ La table `nom_table` ne respecte pas la 3NF → Correction appliquée.")
309
+ Appliquer la correction en éliminant les dépendances transitives.
310
+ corrections_appliquees = True
311
+ Fin Si
312
+ Sinon
313
+ printf("❌ La table `nom_table` ne respecte pas la 2NF → Correction appliquée.")
314
+ Appliquer la correction en éliminant les dépendances partielles.
315
+ corrections_appliquees = True
316
+ Fin Si
317
+ Sinon
318
+ printf("❌ La table `nom_table` ne respecte pas la 1NF → Correction appliquée.")
319
+ Appliquer la correction en atomisant les attributs.
320
+ corrections_appliquees = True
321
+ Fin Si
322
+ Fin Pour
323
+
324
+ Si corrections_appliquees == True alors
325
+ printf("⚠️ Des corrections ont été appliquées durant la vérification.")
326
+ Fin Si
327
+
328
+ printf("🔍 Normalisation terminée.")
329
+ Fin
330
+
331
+ ---
332
+ """
333
+
334
+ response = normalization_validator.run(prompt)
335
+ return response.content.strip()
336
+
337
+
338
+
339
+ def generate_sql_from_normalized_schema(normalized_schema, output_file):
340
+ """Génère les requêtes SQL pour créer et insérer des données dans une base de données normalisée."""
341
+ create_table_statements = []
342
+ insert_data_statements = []
343
+
344
+ # Analyser le schéma normalisé
345
+ tables = normalized_schema.split('📌 Table')
346
+ for table in tables:
347
+ if not table.strip():
348
+ continue
349
+
350
+ # Extraire le nom de la table
351
+ table_name = ""
352
+ table_parts = re.split(r'`| :', table, maxsplit=2)
353
+ if len(table_parts) >= 2:
354
+ table_name = table_parts[1].strip()
355
+
356
+ # Extraire les colonnes et leurs types
357
+ columns = []
358
+ col_types = {}
359
+ if '📝 Colonnes: ' in table:
360
+ columns_line = table.split('📝 Colonnes: ')[1].split('\n')[0]
361
+ columns = [col.strip().split(' (')[0] for col in columns_line.split(', ')]
362
+ col_types = {col.split(' (')[0]: col.split(' (')[1].rstrip(')') for col in columns_line.split(', ')}
363
+
364
+ # Extraire la clé primaire (peut être composée)
365
+ primary_keys = []
366
+ pk_match = re.search(r'🔑 Clé primaire: ([\w, ]+)', table)
367
+ if pk_match:
368
+ primary_keys = [key.strip() for key in pk_match.group(1).split(',')]
369
+
370
+ # Extraire les clés étrangères
371
+ foreign_keys = []
372
+ fk_matches = re.findall(r'🔗 Clé étrangère: (\w+) → (\w+) \((\w+)\)', table)
373
+ for fk_col, ref_table, ref_col in fk_matches:
374
+ foreign_keys.append((fk_col, ref_table, ref_col))
375
+
376
+ # Générer CREATE TABLE
377
+ if table_name and columns:
378
+ create_sql = f"CREATE TABLE {table_name} (\n"
379
+
380
+ # Ajouter les colonnes avec leurs types
381
+ for col in columns:
382
+ col_type = col_types.get(col, "TEXT").upper()
383
+ create_sql += f" {col} {col_type},\n"
384
+
385
+ # Ajouter la clé primaire (peut être composée)
386
+ if primary_keys:
387
+ create_sql += f" PRIMARY KEY ({', '.join(primary_keys)}),\n"
388
+
389
+ # Ajouter les clés étrangères
390
+ for fk in foreign_keys:
391
+ create_sql += f" FOREIGN KEY ({fk[0]}) REFERENCES {fk[1]}({fk[2]}),\n"
392
+
393
+ # Nettoyer les virgules finales
394
+ create_sql = re.sub(r',\n$', '\n', create_sql) + ");"
395
+ create_table_statements.append(create_sql)
396
+
397
+ # Extraire les données et les insérer en une seule requête
398
+ if '📋 Données :' in table:
399
+ data_section = table.split('📋 Données :')[1].split('\n\n')[0]
400
+ data_rows = re.findall(r' - (.*?)\n', data_section)
401
+
402
+ if data_rows:
403
+ insert_sql = f"INSERT INTO {table_name} ("
404
+ insert_sql += ', '.join(columns) + ") VALUES\n"
405
+
406
+ formatted_values_list = []
407
+ for row in data_rows:
408
+ # Nettoyer les guillemets simples et backticks
409
+ values = [v.strip().strip("'`") for v in row.split(', ')]
410
+
411
+ formatted_values = []
412
+ for i, value in enumerate(values):
413
+ col_type = col_types.get(columns[i], "TEXT").upper()
414
+ if col_type in ['TEXT', 'DATE']:
415
+ formatted_values.append(f"'{value}'")
416
+ else:
417
+ formatted_values.append(f"{value}")
418
+
419
+ formatted_values_list.append(f"({', '.join(formatted_values)})")
420
+
421
+ insert_sql += ",\n".join(formatted_values_list) + ";"
422
+ insert_data_statements.append(insert_sql)
423
+
424
+ # Ajouter une ligne vide après les insertions pour cette table
425
+ insert_data_statements.append("")
426
+
427
+ # Écrire dans le fichier
428
+ with open(output_file, 'w', encoding='utf-8') as f:
429
+ f.write("-- Structure de la base de données\n")
430
+ f.write("\n".join(create_table_statements))
431
+ f.write("\n\n-- Migrer les données de l'ancienne bdd\n")
432
+ f.write("\n".join(insert_data_statements))
433
+
434
+
435
+
436
+
437
+ # --- Exécuter le script ---
438
+ def main():
439
+ parser = argparse.ArgumentParser(description="Outil de transformation de fichier vers base SQLite et normalisation de BDD")
440
+ parser.add_argument('--input', type=str, required=True, help="Chemin vers le fichier d'entrée (peut être .db, .json, .csv, .xls ou .xlsx)")
441
+ parser.add_argument('output_file', type=str, help='Chemin vers le fichier SQL de sortie')
442
+ args = parser.parse_args()
443
+
444
+ # Préparer la base de données selon le format
445
+ prepared_db_path = preparer_bdd(args.input)
446
+ if not prepared_db_path:
447
+ sys.exit("❌ Erreur lors de la préparation de la base de données.")
448
+ print(f"✅ Base de données prête : {prepared_db_path}")
449
+
450
+ # Nettoyer la base de données
451
+ clean_database(prepared_db_path)
452
+ print(f"✅ Base de données nettoyée : {prepared_db_path}")
453
+
454
+ # Extraire la structure et les données de la base SQLite préparée
455
+ output_bdd = extraire_bdd(prepared_db_path)
456
+ if not output_bdd:
457
+ sys.exit("❌ Erreur dans l'extraction de la base de données.")
458
+
459
+ # Mise en place de l'auto-correction et auto-vérification (self-consistency)
460
+ proposition_normalisee = output_bdd
461
+ while True:
462
+ resultat = analyser_bdd(proposition_normalisee)
463
+ verification = verifier_normalisation(resultat, output_bdd)
464
+ # Si des corrections sont détectées, mettre à jour la proposition sans afficher le résultat
465
+ if "⚠️ Des corrections ont été appliquées durant la vérification." in verification:
466
+ proposition_normalisee = verification
467
+ else:
468
+ # Afficher uniquement le résultat final correct et sortir de la boucle
469
+ print("\n🔍 Résultat final de l'analyse :\n", resultat)
470
+ print("\n✅ Vérification de la normalisation :\n", verification)
471
+ break
472
+
473
+ # Générer le fichier SQL final
474
+ generate_sql_from_normalized_schema(resultat, args.output_file)
475
+ print(f"✅ Fichier SQL généré: {args.output_file}")
476
+
477
+ if __name__ == "__main__":
478
+ main()
479
+