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

Update src/module/Validation_Contract.py

Browse files
Files changed (1) hide show
  1. src/module/Validation_Contract.py +50 -13
src/module/Validation_Contract.py CHANGED
@@ -135,7 +135,7 @@ class DataExplorer:
135
  def show_transformation_report(self):
136
  """Rapport visuel de transformation ETL"""
137
  st.markdown("---")
138
- st.markdown("### 🏗️ Rapport de Transformation ETL")
139
 
140
  if not self.transformation_report:
141
  st.warning("Aucun rapport de transformation disponible.")
@@ -193,12 +193,12 @@ class DataExplorer:
193
  st.plotly_chart(fig_null, use_container_width=True, key=f"etl_chart_null_{unique_id}")
194
 
195
  # Détail des transformations
196
- with st.expander("📊 Détail des Transformations par Table"):
197
  for table_name, report in self.transformation_report.items():
198
- st.markdown(f"#### 📋 {table_name.upper()}")
199
 
200
  if report.get('corrections_applied'):
201
- st.markdown("**✅ Corrections appliquées :**")
202
  for correction in report['corrections_applied']:
203
  st.write(f"- {correction}")
204
  else:
@@ -212,6 +212,43 @@ class DataExplorer:
212
  col3.metric("Nulls comblés", meta['null_cells_raw'] - meta['null_cells_clean'])
213
 
214
  st.markdown("---")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
 
216
  # =====================================================
217
  # ============= MOTEUR ETL AMÉLIORÉ ===================
@@ -341,7 +378,7 @@ class ETL_Engine:
341
  nb_invalid_age = mask_age_invalid.sum()
342
  if nb_invalid_age > 0:
343
  df_clean = df_clean[~mask_age_invalid].copy()
344
- corrections.append(f"✂️ Supprimé {nb_invalid_age} patients avec âge aberrant (<0 ou >120)")
345
 
346
  # 2. Déduplification (nom + prénom)
347
  if 'nom' in df_clean.columns and 'prenom' in df_clean.columns:
@@ -349,7 +386,7 @@ class ETL_Engine:
349
  df_clean = df_clean.drop_duplicates(subset=['nom', 'prenom'], keep='first')
350
  nb_removed = nb_before - len(df_clean)
351
  if nb_removed > 0:
352
- corrections.append(f"✂️ Supprimé {nb_removed} doublons (nom+prénom)")
353
 
354
  # 3. Filtrer dates de naissance futures
355
  if 'date_naissance' in df_clean.columns:
@@ -357,7 +394,7 @@ class ETL_Engine:
357
  nb_future = mask_future.sum()
358
  if nb_future > 0:
359
  df_clean = df_clean[~mask_future].copy()
360
- corrections.append(f"✂️ Supprimé {nb_future} patients avec date de naissance future")
361
 
362
  return df_clean, corrections
363
 
@@ -382,7 +419,7 @@ class ETL_Engine:
382
  (df_clean.loc[mask_inverted, 'date_sortie'] -
383
  df_clean.loc[mask_inverted, 'date_entree']).dt.days
384
 
385
- corrections.append(f"🔄 Inversé {nb_inverted} paires de dates (sortie < entrée)")
386
 
387
  # 2. Normaliser gravité (forcer entre 1 et 5)
388
  if 'gravite_1_5' in df_clean.columns:
@@ -393,7 +430,7 @@ class ETL_Engine:
393
  # Clip entre 1 et 5
394
  df_clean.loc[mask_invalid_grav, 'gravite_1_5'] = \
395
  df_clean.loc[mask_invalid_grav, 'gravite_1_5'].clip(1, 5)
396
- corrections.append(f"🔧 Normalisé {nb_invalid_grav} gravités hors norme (1-5)")
397
 
398
  # 3. Supprimer séjours avec durée négative (même après correction dates)
399
  if 'duree_sejour_jours' in df_clean.columns:
@@ -402,7 +439,7 @@ class ETL_Engine:
402
 
403
  if nb_negative > 0:
404
  df_clean = df_clean[~mask_negative_duration].copy()
405
- corrections.append(f"✂️ Supprimé {nb_negative} séjours avec durée négative")
406
 
407
  return df_clean, corrections
408
 
@@ -427,7 +464,7 @@ class ETL_Engine:
427
  if 'taux_occupation' in df_clean.columns:
428
  df_clean.loc[mask_suroccup, 'taux_occupation'] = 1.0
429
 
430
- corrections.append(f"🔧 Corrigé {nb_suroccup} cas de suroccupation (plafonné à capacité)")
431
 
432
  # 2. Filtrer taux d'occupation aberrants (>1.5 = 150%)
433
  if 'taux_occupation' in df_clean.columns:
@@ -436,7 +473,7 @@ class ETL_Engine:
436
 
437
  if nb_invalid_rate > 0:
438
  df_clean = df_clean[~mask_invalid_rate].copy()
439
- corrections.append(f"✂️ Supprimé {nb_invalid_rate} lignes avec taux >150%")
440
 
441
  return df_clean, corrections
442
 
@@ -497,7 +534,7 @@ class ETL_Engine:
497
  worksheets = spreadsheet.worksheets()
498
  relevant_sheets = [ws for ws in worksheets if ws.title.lower() not in ['', 'sheet1', 'feuil1']]
499
 
500
- progress_bar = st.progress(0, text="🔄 ETL Engine: Extraction...")
501
 
502
  for i, ws in enumerate(relevant_sheets):
503
  raw_data = ws.get_all_values()
 
135
  def show_transformation_report(self):
136
  """Rapport visuel de transformation ETL"""
137
  st.markdown("---")
138
+ st.markdown("### Rapport de Transformation ETL")
139
 
140
  if not self.transformation_report:
141
  st.warning("Aucun rapport de transformation disponible.")
 
193
  st.plotly_chart(fig_null, use_container_width=True, key=f"etl_chart_null_{unique_id}")
194
 
195
  # Détail des transformations
196
+ with st.expander("Détail des Transformations par Table"):
197
  for table_name, report in self.transformation_report.items():
198
+ st.markdown(f"#### {table_name.upper()}")
199
 
200
  if report.get('corrections_applied'):
201
+ st.markdown("** Corrections appliquées :**")
202
  for correction in report['corrections_applied']:
203
  st.write(f"- {correction}")
204
  else:
 
212
  col3.metric("Nulls comblés", meta['null_cells_raw'] - meta['null_cells_clean'])
213
 
214
  st.markdown("---")
215
+ # [AJOUTER CECI À LA FIN DE LA MÉTHODE show_transformation_report]
216
+
217
+ # --- EXPORT CSV DES MÉTRIQUES ---
218
+ st.markdown("---")
219
+ st.subheader("📥 Export du Rapport ETL")
220
+
221
+ # 1. Préparation des données pour le CSV
222
+ export_data = []
223
+ for name, meta in self.metadata.items():
224
+ # Récupération des corrections sous forme de texte
225
+ report = self.transformation_report.get(name, {})
226
+ corrections_txt = " | ".join(report.get('corrections_applied', []))
227
+
228
+ row = {
229
+ "Table": name,
230
+ "Lignes Brutes": meta['rows_raw'],
231
+ "Lignes Nettoyées": meta['rows_clean'],
232
+ "Lignes Supprimées": meta['rows_removed'],
233
+ "Anomalies Avant": meta['anomalies_raw'],
234
+ "Anomalies Après": meta['anomalies_clean'],
235
+ "Cellules Vides (Nulls)": meta['null_cells_clean'],
236
+ "Corrections Appliquées": corrections_txt
237
+ }
238
+ export_data.append(row)
239
+
240
+ df_export = pd.DataFrame(export_data)
241
+
242
+ # 2. Bouton de téléchargement
243
+ csv = df_export.to_csv(index=False).encode('utf-8-sig') # utf-8-sig pour Excel
244
+
245
+ st.download_button(
246
+ label="📄 Télécharger le Rapport de Transformation (CSV)",
247
+ data=csv,
248
+ file_name=f"rapport_etl_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
249
+ mime="text/csv",
250
+ key=f"dl_btn_etl_{unique_id}" # Utilise l'ID unique généré plus haut
251
+ )
252
 
253
  # =====================================================
254
  # ============= MOTEUR ETL AMÉLIORÉ ===================
 
378
  nb_invalid_age = mask_age_invalid.sum()
379
  if nb_invalid_age > 0:
380
  df_clean = df_clean[~mask_age_invalid].copy()
381
+ corrections.append(f" Supprimé {nb_invalid_age} patients avec âge aberrant (<0 ou >120)")
382
 
383
  # 2. Déduplification (nom + prénom)
384
  if 'nom' in df_clean.columns and 'prenom' in df_clean.columns:
 
386
  df_clean = df_clean.drop_duplicates(subset=['nom', 'prenom'], keep='first')
387
  nb_removed = nb_before - len(df_clean)
388
  if nb_removed > 0:
389
+ corrections.append(f" Supprimé {nb_removed} doublons (nom+prénom)")
390
 
391
  # 3. Filtrer dates de naissance futures
392
  if 'date_naissance' in df_clean.columns:
 
394
  nb_future = mask_future.sum()
395
  if nb_future > 0:
396
  df_clean = df_clean[~mask_future].copy()
397
+ corrections.append(f" Supprimé {nb_future} patients avec date de naissance future")
398
 
399
  return df_clean, corrections
400
 
 
419
  (df_clean.loc[mask_inverted, 'date_sortie'] -
420
  df_clean.loc[mask_inverted, 'date_entree']).dt.days
421
 
422
+ corrections.append(f" Inversé {nb_inverted} paires de dates (sortie < entrée)")
423
 
424
  # 2. Normaliser gravité (forcer entre 1 et 5)
425
  if 'gravite_1_5' in df_clean.columns:
 
430
  # Clip entre 1 et 5
431
  df_clean.loc[mask_invalid_grav, 'gravite_1_5'] = \
432
  df_clean.loc[mask_invalid_grav, 'gravite_1_5'].clip(1, 5)
433
+ corrections.append(f" Normalisé {nb_invalid_grav} gravités hors norme (1-5)")
434
 
435
  # 3. Supprimer séjours avec durée négative (même après correction dates)
436
  if 'duree_sejour_jours' in df_clean.columns:
 
439
 
440
  if nb_negative > 0:
441
  df_clean = df_clean[~mask_negative_duration].copy()
442
+ corrections.append(f"Supprimé {nb_negative} séjours avec durée négative")
443
 
444
  return df_clean, corrections
445
 
 
464
  if 'taux_occupation' in df_clean.columns:
465
  df_clean.loc[mask_suroccup, 'taux_occupation'] = 1.0
466
 
467
+ corrections.append(f" Corrigé {nb_suroccup} cas de suroccupation (plafonné à capacité)")
468
 
469
  # 2. Filtrer taux d'occupation aberrants (>1.5 = 150%)
470
  if 'taux_occupation' in df_clean.columns:
 
473
 
474
  if nb_invalid_rate > 0:
475
  df_clean = df_clean[~mask_invalid_rate].copy()
476
+ corrections.append(f" Supprimé {nb_invalid_rate} lignes avec taux >150%")
477
 
478
  return df_clean, corrections
479
 
 
534
  worksheets = spreadsheet.worksheets()
535
  relevant_sheets = [ws for ws in worksheets if ws.title.lower() not in ['', 'sheet1', 'feuil1']]
536
 
537
+ progress_bar = st.progress(0, text=" ETL Engine: Extraction...")
538
 
539
  for i, ws in enumerate(relevant_sheets):
540
  raw_data = ws.get_all_values()