import os import datetime as dt import pandas as pd import streamlit as st from dotenv import load_dotenv import mysql.connector as mc # ---------------------- # Config de la page # ---------------------- st.set_page_config(page_title="Injection de données de test", page_icon="🧪", layout="centered") st.title("🧪 Injecteur de relevés de test (Sondes)") st.caption("Crée ~10 lignes au-dessus d'un seuil pour tester les alertes") # ---------------------- # Connexion MySQL depuis .env # ---------------------- @st.cache_resource(show_spinner=False) def get_connection(): load_dotenv() try: cnx = mc.connect( host=os.getenv("DB_HOST"), user=os.getenv("DB_USER"), password=os.getenv("DB_PASSWORD"), database=os.getenv("DB_NAME2"), autocommit=True, ) return cnx except Exception as e: st.error(f"Échec de connexion MySQL : {e}") raise # ---------------------- # Helpers : liste des sondes # ---------------------- @st.cache_data(ttl=60, show_spinner=False) def list_sondes(site: str) -> list: """ Retourne la liste des sondes pour un site donné. 1) Sondes actives dans Chambres_froides 2) À défaut : DISTINCT Sonde dans la table de mesures {site} Affiche des messages DEBUG en cas d'erreur ou de liste vide. """ cnx = get_connection() cur = cnx.cursor() # 1) Chambres_froides try: cur.execute( """ SELECT Sonde FROM `Chambres_froides` WHERE Lieu = %s AND (Etat = 'ON' OR Etat = 1) ORDER BY Sonde """, (site,), ) rows = [r[0] for r in cur.fetchall()] if rows: st.info(f"[DEBUG] {len(rows)} sonde(s) trouvée(s) dans Chambres_froides pour Lieu='{site}'.") return rows else: st.info(f"[DEBUG] Aucune sonde active dans Chambres_froides pour Lieu='{site}'.") except Exception as e: st.error(f"[DEBUG] Erreur Chambres_froides : {e}") # 2) Fallback : table de mesures `{site}` try: cur.execute(f"SELECT DISTINCT Sonde FROM `{site}` ORDER BY Sonde LIMIT 200") rows = [r[0] for r in cur.fetchall()] if rows: st.info(f"[DEBUG] {len(rows)} sonde(s) trouvée(s) dans la table `{site}`.") return rows else: st.info(f"[DEBUG] Aucune sonde trouvée dans la table `{site}`.") except Exception as e: st.error(f"[DEBUG] Erreur table `{site}` : {e}") return [] # ---------------------- # Helper : récupérer Temp_Max pour une sonde donnée # ---------------------- @st.cache_data(ttl=60, show_spinner=False) def get_temp_max(site: str, sonde: str): """ Retourne Temp_Max pour (site, sonde) depuis Chambres_froides. Renvoie None si non trouvé, avec messages DEBUG. """ try: cnx = get_connection() cur = cnx.cursor() cur.execute( """ SELECT Temp_Max FROM `Chambres_froides` WHERE Lieu = %s AND Sonde = %s LIMIT 1 """, (site, sonde), ) row = cur.fetchone() if row and row[0] is not None: st.info(f"[DEBUG] Temp_Max trouvé pour {site}/{sonde} : {row[0]}°C.") return float(row[0]) else: st.info(f"[DEBUG] Aucun Temp_Max trouvé dans Chambres_froides pour {site}/{sonde}.") except Exception as e: st.error(f"[DEBUG] Erreur get_temp_max : {e}") return None return None # ---------------------- # UI paramètres # ---------------------- with st.sidebar: st.header("Paramètres") site = st.selectbox("Site (table)", ["Saclay", "Meudon"], index=0) # Sélecteur de sonde depuis la liste options_sondes = list_sondes(site) if not options_sondes: st.warning("Aucune sonde trouvée pour ce site. Vous pouvez saisir un nom manuel.") sonde = st.text_input("Nom de la sonde", value="TEST_Chambre1") else: sonde = st.selectbox("Sonde", options_sondes) st.subheader("Température") # Auto-remplissage du seuil depuis la base et verrouillage par défaut _temp_db = get_temp_max(site, sonde) if _temp_db is None: st.warning("Temp_Max introuvable en base ; valeur par défaut 6.0°C.") _temp_db = 6.0 allow_edit = st.checkbox("Autoriser la modification du seuil", value=False) temp_max = st.number_input("Seuil (Temp_Max)", value=float(_temp_db), step=0.1, disabled=not allow_edit) delta = st.number_input("Delta au-dessus du seuil", value=1.0, step=0.1) absolute_override = st.checkbox("Définir une température absolue à la place") absolute_temp = st.number_input( "Température absolue (si coché)", value=12.5, step=0.1, disabled=not absolute_override ) st.subheader("Série temporelle") rows = st.number_input("Nombre de points", min_value=1, max_value=200, value=10, step=1) step_min = st.number_input("Pas (minutes)", min_value=1, max_value=120, value=5, step=1) start_offset = st.number_input("Début : il y a (minutes)", min_value=0, max_value=1440, value=5, step=5) st.markdown("---") st.caption("Nettoyage rapide") cleanup_scope = st.selectbox("Supprimer", ["Cette sonde", "Toutes les TEST_ des dernières 24h"]) do_cleanup = st.button("🧹 Supprimer les données de test") col1, col2 = st.columns(2) # ---------------------- # Actions # ---------------------- if col1.button("🚀 Injecter les données"): try: cnx = get_connection() cur = cnx.cursor() # Calcul des timestamps et de la valeur now = dt.datetime.now() t0 = now - dt.timedelta(minutes=int(start_offset)) if absolute_override: value = float(absolute_temp) else: value = float(temp_max) + float(delta) # Préparation batch INSERT sql = f"INSERT INTO `{site}` (Sonde, Temperature, Date) VALUES (%s,%s,%s)" batch = [] for i in range(int(rows)): ts = t0 + dt.timedelta(minutes=i * int(step_min)) batch.append((sonde, value, ts.strftime("%Y-%m-%d %H:%M:%S"))) cur.executemany(sql, batch) st.success(f"{len(batch)} lignes insérées dans `{site}` pour **{sonde}** à **{value}°C**.") # Aperçu des données insérées cur.execute( f""" SELECT Id, Sonde, Temperature, Date FROM `{site}` WHERE Sonde = %s AND Date >= %s AND Date <= %s ORDER BY Date DESC LIMIT 50 """, ( sonde, (t0 - dt.timedelta(minutes=1)).strftime("%Y-%m-%d %H:%M:%S"), (t0 + dt.timedelta(minutes=int(rows)*int(step_min) + 1)).strftime("%Y-%m-%d %H:%M:%S"), ), ) rows_preview = cur.fetchall() if rows_preview: df = pd.DataFrame(rows_preview, columns=["Id", "Sonde", "Temperature", "Date"]) st.dataframe(df, use_container_width=True, hide_index=True) else: st.info("Aucune ligne trouvée pour l'aperçu (vérifiez les filtres/horaires).") except Exception as e: st.error(f"Erreur lors de l'injection : {e}") # Nettoyage def cleanup(): cnx = get_connection() cur = cnx.cursor() if cleanup_scope == "Cette sonde": cur.execute(f"DELETE FROM `{site}` WHERE Sonde = %s", (sonde,)) st.success(f"Données supprimées pour la sonde **{sonde}** dans `{site}`.") else: cur.execute( f"DELETE FROM `{site}` WHERE Sonde LIKE 'TEST\_%' ESCAPE '\\' AND Date >= NOW() - INTERVAL 1 DAY" ) st.success(f"Toutes les sondes **TEST_** des dernières 24h supprimées dans `{site}`.") if do_cleanup: try: cleanup() except Exception as e: st.error(f"Erreur de nettoyage : {e}")