Spaces:
Sleeping
Sleeping
Update src/module/Validation_Contract.py
Browse files
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("###
|
| 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("
|
| 197 |
for table_name, report in self.transformation_report.items():
|
| 198 |
-
st.markdown(f"####
|
| 199 |
|
| 200 |
if report.get('corrections_applied'):
|
| 201 |
-
st.markdown("**
|
| 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"
|
| 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"
|
| 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"
|
| 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"
|
| 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"
|
| 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"
|
| 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"
|
| 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"
|
| 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="
|
| 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()
|