Spaces:
Sleeping
Sleeping
Update src/module/add_data.py
Browse files- src/module/add_data.py +144 -0
src/module/add_data.py
CHANGED
|
@@ -0,0 +1,144 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
MODULE: DATA INJECTOR (FLUX CONTINU)
|
| 3 |
+
====================================
|
| 4 |
+
Responsabilité : Injection contrôlée de nouveaux lots de données (quelques centaines de lignes).
|
| 5 |
+
Mode : Append (Ajout à la suite sans écraser).
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import pandas as pd
|
| 9 |
+
import numpy as np
|
| 10 |
+
import datetime as dt
|
| 11 |
+
from faker import Faker
|
| 12 |
+
import streamlit as st
|
| 13 |
+
|
| 14 |
+
class DataInjector:
|
| 15 |
+
def __init__(self, client, sheet_name):
|
| 16 |
+
self.client = client
|
| 17 |
+
self.sheet_name = sheet_name
|
| 18 |
+
self.fake = Faker("fr_FR")
|
| 19 |
+
# Services de l'hôpital
|
| 20 |
+
self.svc_codes = ["URG", "MED", "PED", "REA", "INF", "ISO", "CAR", "NEU", "TRA"]
|
| 21 |
+
|
| 22 |
+
def _append_to_sheet(self, df, worksheet_name):
|
| 23 |
+
"""Ajoute les données à la suite des lignes existantes dans Google Sheets"""
|
| 24 |
+
try:
|
| 25 |
+
sh = self.client.open(self.sheet_name)
|
| 26 |
+
worksheet = sh.worksheet(worksheet_name)
|
| 27 |
+
|
| 28 |
+
# Conversion en string pour l'API Google Sheets
|
| 29 |
+
df_export = df.copy()
|
| 30 |
+
for col in df_export.select_dtypes(include=['datetime64', 'object', 'datetimetz']).columns:
|
| 31 |
+
df_export[col] = df_export[col].astype(str)
|
| 32 |
+
|
| 33 |
+
# Extraction des valeurs brutes
|
| 34 |
+
values = df_export.values.tolist()
|
| 35 |
+
|
| 36 |
+
# Appel API
|
| 37 |
+
worksheet.append_rows(values)
|
| 38 |
+
return True
|
| 39 |
+
except Exception as e:
|
| 40 |
+
st.error(f"❌ Erreur lors de l'ajout dans '{worksheet_name}': {e}")
|
| 41 |
+
return False
|
| 42 |
+
|
| 43 |
+
def generate_batch(self, days=7):
|
| 44 |
+
"""
|
| 45 |
+
Génère un lot de données pour X jours.
|
| 46 |
+
Volume estimé pour 7 jours : ~63 lignes Ressources + ~400 lignes Admissions + Stocks.
|
| 47 |
+
"""
|
| 48 |
+
# On simule à partir d'aujourd'hui pour avoir des données "fraîches"
|
| 49 |
+
start_date = dt.date.today()
|
| 50 |
+
dates = pd.date_range(start_date, periods=days, freq="D")
|
| 51 |
+
|
| 52 |
+
# 1. SCÉNARIOS (Météo/Épidémie)
|
| 53 |
+
scen_rows = []
|
| 54 |
+
for d in dates:
|
| 55 |
+
# Petit hasard pour rendre ça vivant
|
| 56 |
+
r = np.random.random()
|
| 57 |
+
if r < 0.1: sc = "Grippe"
|
| 58 |
+
elif r < 0.05: sc = "Canicule"
|
| 59 |
+
else: sc = "Normal"
|
| 60 |
+
scen_rows.append({"date": d, "scenario": sc, "intensity": 1 if sc != "Normal" else 0})
|
| 61 |
+
df_scen = pd.DataFrame(scen_rows)
|
| 62 |
+
|
| 63 |
+
# 2. ADMISSIONS (Le flux patients)
|
| 64 |
+
adm_rows = []
|
| 65 |
+
for d in dates:
|
| 66 |
+
for svc in self.svc_codes:
|
| 67 |
+
# Variation aléatoire pour tester la sensibilité de l'IA
|
| 68 |
+
n_patients = np.random.poisson(lam=random.randint(3, 12))
|
| 69 |
+
adm_rows.append({
|
| 70 |
+
"date": d,
|
| 71 |
+
"service_code": svc,
|
| 72 |
+
"nb_admissions": n_patients
|
| 73 |
+
})
|
| 74 |
+
df_adm = pd.DataFrame(adm_rows)
|
| 75 |
+
|
| 76 |
+
# 3. RESSOURCES (L'état des lits)
|
| 77 |
+
res_rows = []
|
| 78 |
+
for d in dates:
|
| 79 |
+
for svc in self.svc_codes:
|
| 80 |
+
# Simulation d'une occupation variable
|
| 81 |
+
lits_tot = 150
|
| 82 |
+
occup = np.random.randint(80, 155) # Parfois ça déborde (>150)
|
| 83 |
+
|
| 84 |
+
# Injection d'anomalies pour tester le module DQ
|
| 85 |
+
is_anomaly = 1 if occup > lits_tot else 0
|
| 86 |
+
anomaly_type = "suroccupation" if is_anomaly else None
|
| 87 |
+
|
| 88 |
+
res_rows.append({
|
| 89 |
+
"date": d,
|
| 90 |
+
"service_code": svc,
|
| 91 |
+
"lits_total": lits_tot,
|
| 92 |
+
"lits_occupes": occup,
|
| 93 |
+
"personnel_disponible": np.random.randint(20, 35),
|
| 94 |
+
"personnel_requis": 30,
|
| 95 |
+
"taux_occupation": round(occup/lits_tot, 2),
|
| 96 |
+
"dq_is_anomaly": is_anomaly,
|
| 97 |
+
"dq_anomaly_type": anomaly_type
|
| 98 |
+
})
|
| 99 |
+
df_res = pd.DataFrame(res_rows)
|
| 100 |
+
|
| 101 |
+
return df_scen, df_adm, df_res
|
| 102 |
+
|
| 103 |
+
def run(self, days_to_add):
|
| 104 |
+
scen, adm, res = self.generate_batch(days=days_to_add)
|
| 105 |
+
|
| 106 |
+
total_rows = len(scen) + len(adm) + len(res)
|
| 107 |
+
|
| 108 |
+
with st.status(f"🚀 Injection de {total_rows} nouvelles lignes...", expanded=True) as status:
|
| 109 |
+
st.write("📝 Ajout au calendrier...")
|
| 110 |
+
self._append_to_sheet(scen, "scenarios_calendrier")
|
| 111 |
+
|
| 112 |
+
st.write(f"valeurs : {len(adm)} admissions...")
|
| 113 |
+
self._append_to_sheet(adm, "admissions_jour")
|
| 114 |
+
|
| 115 |
+
st.write(f"valeurs : {len(res)} relevés de ressources...")
|
| 116 |
+
self._append_to_sheet(res, "ressources_jour")
|
| 117 |
+
|
| 118 |
+
status.update(label="✅ Injection terminée !", state="complete")
|
| 119 |
+
|
| 120 |
+
return total_rows
|
| 121 |
+
|
| 122 |
+
# --- INTERFACE ---
|
| 123 |
+
def show_data_injector(client, sheet_name):
|
| 124 |
+
import random # Nécessaire ici si non importé en haut
|
| 125 |
+
|
| 126 |
+
st.markdown("## 💉 INJECTEUR DE FLUX (LIVE TEST)")
|
| 127 |
+
st.caption("Ajoute des données récentes pour tester la réaction de l'IA et du Dashboard.")
|
| 128 |
+
|
| 129 |
+
col1, col2 = st.columns([2, 1])
|
| 130 |
+
|
| 131 |
+
with col1:
|
| 132 |
+
days = st.slider("Jours à simuler", min_value=1, max_value=14, value=7,
|
| 133 |
+
help="7 jours génèrent environ 500 nouvelles lignes.")
|
| 134 |
+
|
| 135 |
+
with col2:
|
| 136 |
+
# Estimation du volume
|
| 137 |
+
st.metric("Volume estimé", f"~{days * 9 * 6} lignes", delta="Nouveaux cas")
|
| 138 |
+
|
| 139 |
+
if st.button("⚡ Injecter les données maintenant", type="primary"):
|
| 140 |
+
injector = DataInjector(client, sheet_name)
|
| 141 |
+
count = injector.run(days)
|
| 142 |
+
|
| 143 |
+
st.success(f"Opération réussie. **{count}** nouvelles lignes ont été ajoutées à la base.")
|
| 144 |
+
st.warning("👉 N'oubliez pas de relancer l'ETL (Feature Engineering) pour que l'IA voie ces nouvelles données.")
|